RSA между C# и BCrypt C

Я пытаюсь использовать RSA 2048 для шифрования между моими программами на C# и C. Для этого я встраиваю закрытый ключ в свою программу на C, а открытый ключ — в программу на C#. Затем моя программа на C# шифрует строку и отправляет ее в мою программу на C для расшифровки. Проблема в том, что я получаю ошибку 0xc000000d при вызове функции BCryptDecrypt внутри моей программы на C, когда я использую любой параметр заполнения, кроме BCRYPT_PAD_NONE. Проблема в том, что в библиотеке C# System.Security.Cryptography есть только опции для использования PKCS1 и OAEP, которые не кажутся совместимыми с версией BCrypt (хотя мой открытый ключ сомнительно). Я определил это, потому что установка var encryptedData = rsa.Encrypt(dataToEncryptBytes, true); в true означает, что он использует OAEP, а false означает, что он использует PKCS1, и когда я внес эти изменения, я также изменил оба вызова функции BCryptDecrypt внутри моей функции DecryptRsaData, где это всегда приводит к ошибке 0xc000000d, также известной как INVALID_PARAMETER. Затем, когда я перешел на использование BCRYPT_PAD_NONE, это не привело к ошибке (поскольку данные больше не проверяются), но полученный PUCHAR после расшифровки является тарабарщиной и не содержит моей исходной строки.

Первоначально я сгенерировал ключи внутри своей программы на C#, а затем скопировал их ключи в каждую программу, но это было плохо, поскольку отладка открытого ключа была бы намного проще, поскольку у него всего два параметра. Поэтому вместо этого я решил сгенерировать шестнадцатеричный код, связанный как с закрытым, так и с открытым ключом, внутри моей программы на C, а затем скопировать закрытый ключ в массив беззнаковых символов и скопировать открытый ключ в строку C# для дальнейшей обработки. Ниже описано, как я это сделал.

static void PrintKeyInHex(PUCHAR key, ULONG keySize) {
    for (ULONG i = 0; i < keySize; ++i) {

        DebugMessage("%02X", key[i]); // %02X formats the output as two-digit hexadecimal
        if (i + 1 < keySize) {
            DebugMessage(":"); 
        }
    }
    DebugMessage("\n");
}

NTSTATUS GenerateAndPrintKeys() {
    BCRYPT_ALG_HANDLE hAlgorithm = NULL;
    BCRYPT_KEY_HANDLE hKey = NULL;
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PUCHAR pbPublicKey = NULL, pbPrivateKey = NULL;
    ULONG cbPublicKey = 0, cbPrivateKey = 0, cbData = 0;

    // Open an algorithm provider for RSA.
    status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RSA_ALGORITHM, NULL, 0);
    if (!NT_SUCCESS(status)) {
        DebugMessage("Failed to open algorithm provider.");
        goto Cleanup;
    }

    // Generate the key pair.
    status = BCryptGenerateKeyPair(hAlgorithm, &hKey, 2048, 0);
    if (!NT_SUCCESS(status)) {
        DebugMessage("Failed to generate key pair.");
        goto Cleanup;
    }

    // Finalize the key (make it usable).
    status = BCryptFinalizeKeyPair(hKey, 0);
    if (!NT_SUCCESS(status)) {
        DebugMessage("Failed to finalize key pair.");
        goto Cleanup;
    }

    // Export the public key.
    status = BCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, 0, &cbPublicKey, 0);
    if (!NT_SUCCESS(status)) {
        DebugMessage("Failed to get public key size.");
        goto Cleanup;
    }

    pbPublicKey = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, cbPublicKey, '1Tag');
    if (!pbPublicKey) {
        status = STATUS_NO_MEMORY;
        goto Cleanup;
    }

    status = BCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, pbPublicKey, cbPublicKey, &cbData, 0);
    if (!NT_SUCCESS(status)) {
        DebugMessage("Failed to export public key.");
        goto Cleanup;
    }

    // Export the private key
    status = BCryptExportKey(hKey, NULL, BCRYPT_RSAPRIVATE_BLOB, NULL, 0, &cbPrivateKey, 0);
    if (!NT_SUCCESS(status)) {
        DebugMessage("Failed to get private key size.");
        goto Cleanup;
    }

    pbPrivateKey = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, cbPrivateKey, '2Tag');
    if (!pbPrivateKey) {
        status = STATUS_NO_MEMORY;
        goto Cleanup;
    }

    status = BCryptExportKey(hKey, NULL, BCRYPT_RSAPRIVATE_BLOB, pbPrivateKey, cbPrivateKey, &cbData, 0);
    if (!NT_SUCCESS(status)) {
        DebugMessage("Failed to export private key.");
        goto Cleanup;
    }

    DebugMessage("Public Key: ");
    PrintKeyInHex(pbPublicKey, cbPublicKey);
    DebugMessage("\n \n");

    DebugMessage("Private Key: ");
    PrintKeyInHex(pbPrivateKey, cbPrivateKey);

Cleanup:
    if (pbPublicKey) ExFreePoolWithTag(pbPublicKey, '1Tag');
    if (pbPrivateKey) ExFreePoolWithTag(pbPrivateKey, '2Tag');
    if (hKey) BCryptDestroyKey(hKey);
    if (hAlgorithm) BCryptCloseAlgorithmProvider(hAlgorithm, 0);

    return status;
}

Это гарантировало правильность моего закрытого ключа, а это просто означает, что мне нужно было заставить открытый ключ работать с алгоритмом System.Security.Cryptography RSA C#. Но поскольку я знал, что открытый ключ заканчивается модулем (256 байт), то экспонентой (3 байта) в конце было довольно легко. Затем, поскольку я не был уверен в порядке байтов (также просто для того, чтобы охватить все основы), я решил перебрать его, попробовав все 4 комбинации байтов и отправив их в свою программу на C. Под этим я подразумеваю, что я бы вызвал следующий метод C# EncryptDataWithPublicKey 4 раза с i от 0 до 3, гарантируя, что какие-либо ошибки не связаны с неправильным порядком байтов. Несмотря на это, я все равно получал одни и те же проблемы, описанные в первом абзаце, все 4 раза.

        public static byte[] EncryptDataWithPublicKey(string dataToEncrypt, int i)
        {
            RSAParameters rsaParams = ConvertHexToRSAParameters(hexPublicKey, i);
            using (var rsa = new RSACryptoServiceProvider())
            {
                // Load the RSA public key
                rsa.ImportParameters(rsaParams);

                // Convert the data to encrypt to a byte array
                var dataToEncryptBytes = Encoding.UTF8.GetBytes(dataToEncrypt);

                // Encrypt the data. OAEP padding is recommended for new applications.
                var encryptedData = rsa.Encrypt(dataToEncryptBytes, false); // Set to true to use OAEP padding

                return encryptedData;
            }
        }

        private static RSAParameters ConvertHexToRSAParameters(string hexString, int i)
        {
            byte[] bytes = HexStringToByteArray(hexString);
            Console.WriteLine("BytesSize:" + bytes.Length);
            bytes.ToList().ForEach(i => Console.Write(i.ToString() + " "));
            RSAParameters rsaParams = new RSAParameters();

            // Extract the modulus and exponent from the blob
            byte[] exponent = ExtractExponent(bytes, 3); // Assuming 3 bytes for exponent
            byte[] modulus = ExtractModulus(bytes, 256); 

            if (i == 1)
            {
                Array.Reverse(exponent);
                Array.Reverse(modulus);
            }
            else if (i == 2)
            {
                Array.Reverse(exponent);
            }
            else if (i == 3)
            {
                Array.Reverse(modulus);
            }

            rsaParams.Exponent = exponent;
            rsaParams.Modulus = modulus;

            return rsaParams;
        }

В любом случае, как только моя программа на C пытается расшифровать исходную строку, я получаю статус 0xc000000d при использовании BCRYPT_PAD_PKCS1 или BCRYPT_PAD_OAEP, в то время как BCRYPT_PAD_NONE не приводит к проблемам со статусом, но приводит к нечитаемым данным. Я предполагаю, что либо библиотека C, известная как BCrypt BCRYPT_PAD_OAEP и BCRYPT_PAD_PKCS1, несовместима с версией C#, либо я неправильно создаю открытые ключи. Я пришел к такому выводу, поскольку знаю, что закрытый ключ действителен на 100%, поскольку он сгенерирован алгоритмом BCrypt, и я знаю, что шестнадцатеричный код открытого ключа также правильный, но я не уверен, конвертирую ли я шестнадцатеричный ключ открытого ключа в правильный формат. Кроме того, последнее замечание, основанное на предыдущем тестировании функции BCryptImportKeyPair, могло произойти сбой, когда я неправильно отформатировал открытый ключ в своем приложении C#, но я совершенно уверен, что это всего лишь проверка длины, которая говорит мне, что длины моих полей правильны, но опять же, я Я также пробую все возможности порядка байтов, поэтому я действительно не понимаю, в чем может быть проблема. При этом ниже приведен мой код C, который я использую для расшифровки реальных данных.

unsigned char g_privateKeyBlob[] = { 0x52, 0x53, 0x41, 0x32, ... }; // I cut off the rest since its 539 bytes long

static void DebugPrintEncryptedData(UCHAR* data, ULONG length) {
    for (ULONG i = 0; i < length; ++i) {
        DebugMessage("%02X ", data[i]);
        if ((i + 1) % 16 == 0) {
            DebugMessage("\n"); // New line every 16 bytes for readability
        }
    }
    DebugMessage("\n"); // New line at the end
}

                // This code then runs which calls the bellow function and then formats the data in a readable format with the above function
                ULONG privateKeyBlobLength = sizeof(g_privateKeyBlob); // Get the size of your private key blob
                PUCHAR decryptedData = NULL;
                ULONG decryptedDataLength = 0;

                NTSTATUS stat = DecryptRsaData(handshakeData->EncryptedData, 256, g_privateKeyBlob, privateKeyBlobLength, &decryptedData, &decryptedDataLength);
                DebugMessage("FirstDebug Status:%d \n", stat);
                DebugMessage("Status:0x%x \n", stat);
                DebugPrintEncryptedData(decryptedData, decryptedDataLength);
                DebugMessage("Length:%lu \n", decryptedDataLength);
                DebugPrintEncryptedData(handshakeData->EncryptedData, 256);
                DebugMessage("PrivateKeyLength:%lu \n \n", privateKeyBlobLength);

NTSTATUS DecryptRsaData(
    PUCHAR encryptedData, ULONG encryptedDataLength,
    PUCHAR privateKeyBlob, ULONG privateKeyBlobLength,
    PUCHAR* decryptedData, PULONG decryptedDataLength) {

    BCRYPT_ALG_HANDLE algHandle = NULL;
    BCRYPT_KEY_HANDLE keyHandle = NULL;
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    DebugMessage("DecryptRsaData 1 \n");

    // Open an algorithm provider for RSA.
    status = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_RSA_ALGORITHM, NULL, 0);
    if (!NT_SUCCESS(status)) {
        return status;
    }
    DebugMessage("DecryptRsaData 2 \n");

    // Import the private key.
    status = BCryptImportKeyPair(algHandle, NULL, BCRYPT_RSAPRIVATE_BLOB, &keyHandle, privateKeyBlob, privateKeyBlobLength, 0);
    if (!NT_SUCCESS(status)) {
        BCryptCloseAlgorithmProvider(algHandle, 0);
        return status;
    }
    DebugMessage("DecryptRsaData 3 \n");

    // Get the size needed for the decrypted data buffer.
    status = BCryptDecrypt(keyHandle, encryptedData, encryptedDataLength, NULL, NULL, 0, NULL, 0, decryptedDataLength, BCRYPT_PAD_PKCS1); // We are failing here
    if (!NT_SUCCESS(status)) {
         BCryptDestroyKey(keyHandle);
         BCryptCloseAlgorithmProvider(algHandle, 0);
         return status;
    }
    DebugMessage("DecryptRsaData 4 \n");

    // Allocate the buffer for the decrypted data.
    *decryptedData = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, *decryptedDataLength, 'decR');
    if (*decryptedData == NULL) {
        BCryptDestroyKey(keyHandle);
        BCryptCloseAlgorithmProvider(algHandle, 0);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    DebugMessage("DecryptRsaData 5 \n");

    // Perform the decryption.
    status = BCryptDecrypt(keyHandle, encryptedData, encryptedDataLength, NULL, NULL, 0, *decryptedData, *decryptedDataLength, decryptedDataLength, BCRYPT_PAD_PKCS1);
    if (!NT_SUCCESS(status)) {
        ExFreePoolWithTag(*decryptedData, 'decR');
        *decryptedData = NULL;
        *decryptedDataLength = 0;
    }
    DebugMessage("DecryptRsaData 6 \n");

    BCryptDestroyKey(keyHandle);
    BCryptCloseAlgorithmProvider(algHandle, 0);

    return status;
}

Редактировать: Хорошо, я добился некоторых успехов, вот ключи для справки. Я также включил макет открытого ключа в BCrypt.

typedef struct _BCRYPT_RSAKEY_BLOB {
    ULONG Magic;         // A magic number identifying the blob type, for public key, this is BCRYPT_RSAPUBLIC_MAGIC.
    ULONG BitLength;     // The length of the key, in bits.
    ULONG cbPublicExp;   // The length of the public exponent, in bytes.
    ULONG cbModulus;     // The length of the modulus, in bytes.
    // Following this header in memory would be:
    // - The public exponent (of length cbPublicExp)
    // - The modulus (of length cbModulus)
} BCRYPT_RSAKEY_BLOB;

Public Key: 52:53:41:31:00:08:00:00:03:00:00:00:00:01:00:00:00:00:00:00:00:00:00:00:01:00:01:B6:49:39:26:7C:58:59:09:F3:BE:14:10:A8:A2:E1:7A:1B:1E:32:6F:43:65:B5:56:1A:30:24:7D:DB:AA:6A:58:C9:40:AA:B4:91:5C:D3:9B:1C:9D:DD:97:0E:BF:D8:D2:40:1F:C4:27:E4:1E:74:3A:A1:9E:F7:60:B8:C1:47:86:F5:C4:73:F3:A7:DD:AA:F6:6F:81:FA:FD:FE:4E:00:D4:5A:04:2D:24:16:89:D5:03:30:72:36:A5:FE:D2:8E:96:76:EB:04:BC:92:29:3B:77:37:C8:C3:09:27:A9:CD:52:B9:DD:7C:20:AE:60:F8:50:B9:DA:CD:E2:4A:4C:18:7A:66:FC:8E:F7:84:E8:D1:B6:CB:6B:DD:8E:56:F5:99:80:B3:27:44:60:29:2C:3F:1C:72:72:0A:37:FB:FE:37:FA:61:37:2E:A1:EC:35:DE:FA:91:23:A0:91:C4:AC:E4:2F:C9:6D:85:84:94:5A:8A:F3:47:FD:FB:6C:58:7A:62:49:C7:2C:8E:05:E9:4D:79:5B:61:51:CF:2E:1A:4C:22:A5:15:C6:CB:70:8B:D3:E6:0E:1D:C9:62:37:D2:0E:5F:DD:50:B3:05:E1:5B:CE:1A:F5:22:9B:5F:6E:9C:7B:DF:9A:98:19:93:1B:00:93:27:F3:69:43:CD:08:6D:E6:D8:55

Private Key: 52:53:41:32:00:08:00:00:03:00:00:00:00:01:00:00:80:00:00:00:80:00:00:00:01:00:01:B6:49:39:26:7C:58:59:09:F3:BE:14:10:A8:A2:E1:7A:1B:1E:32:6F:43:65:B5:56:1A:30:24:7D:DB:AA:6A:58:C9:40:AA:B4:91:5C:D3:9B:1C:9D:DD:97:0E:BF:D8:D2:40:1F:C4:27:E4:1E:74:3A:A1:9E:F7:60:B8:C1:47:86:F5:C4:73:F3:A7:DD:AA:F6:6F:81:FA:FD:FE:4E:00:D4:5A:04:2D:24:16:89:D5:03:30:72:36:A5:FE:D2:8E:96:76:EB:04:BC:92:29:3B:77:37:C8:C3:09:27:A9:CD:52:B9:DD:7C:20:AE:60:F8:50:B9:DA:CD:E2:4A:4C:18:7A:66:FC:8E:F7:84:E8:D1:B6:CB:6B:DD:8E:56:F5:99:80:B3:27:44:60:29:2C:3F:1C:72:72:0A:37:FB:FE:37:FA:61:37:2E:A1:EC:35:DE:FA:91:23:A0:91:C4:AC:E4:2F:C9:6D:85:84:94:5A:8A:F3:47:FD:FB:6C:58:7A:62:49:C7:2C:8E:05:E9:4D:79:5B:61:51:CF:2E:1A:4C:22:A5:15:C6:CB:70:8B:D3:E6:0E:1D:C9:62:37:D2:0E:5F:DD:50:B3:05:E1:5B:CE:1A:F5:22:9B:5F:6E:9C:7B:DF:9A:98:19:93:1B:00:93:27:F3:69:43:CD:08:6D:E6:D8:55:E0:13:02:06:E2:A0:BB:63:B7:5E:3C:27:8C:75:E4:EA:92:E4:75:A3:2E:9E:AF:C0:8A:87:A0:50:DC:77:1E:B7:D8:3F:7F:9E:42:4D:06:AB:07:3C:77:F5:00:64:8F:C1:F2:86:89:18:B9:4E:B7:FA:26:CE:58:BA:08:B3:B7:86:D3:53:A5:D5:4E:66:D9:CF:3B:18:C2:FB:D0:DE:B5:A2:6F:72:8F:71:D3:D4:0A:36:97:14:9C:ED:AE:03:DC:BC:4E:B4:CF:1F:DC:F5:C7:35:F6:AC:BB:DD:22:AC:CF:B1:BB:83:ED:86:58:E0:72:18:54:EE:A9:8D:76:23:14:03:D0:42:02:F9:4F:E4:FE:00:2A:76:97:6D:1D:F5:6D:F3:27:4C:9D:4D:C6:C2:6A:B9:30:C3:D0:E8:99:F3:C0:6C:ED:25:85:6F:71:CF:D2:0F:F2:18:E5:FC:EA:16:9C:4B:81:C9:3B:4A:DF:84:BF:CD:C4:1F:04:52:2E:77:D7:51:2B:4B:1F:25:14:7C:E4:1B:A2:7B:57:A1:95:49:A0:4B:0A:C6:6B:9E:99:00:29:E9:B0:07:F1:7D:2C:ED:8F:38:20:3A:EA:36:67:53:C5:29:35:2C:63:7A:50:60:3E:70:8D:24:5C:B0:1A:1E:E1:1C:42:1E:D0:C4:6E:03:6E:C7

Выше мы видим, что в начале есть 4, 4-байтовые шестнадцатеричные значения в Little-Endian. Magic и BitLength для нас не имеют значения, но следующие два имеют значение. Здесь мы получаем подтверждение, что Экспонента составляет 3 байта, а Модуль — 256 байт.

            byte[] bytes = HexStringToByteArray(publicKey);
            int bitLength = BitConverter.ToInt32(bytes, 4);
            int cbPublicExp = BitConverter.ToInt32(bytes, 8);
            int cbModulus = BitConverter.ToInt32(bytes, 12);
    
            Console.WriteLine(" \n Exp:");
            byte[] exponent = ExtractExponent(bytes, cbPublicExp, cbModulus); 
            exponent.ToList().ForEach(i => Console.Write(i.ToString() + " "));
            Console.WriteLine(" \n Mod:");
            byte[] modulus = ExtractModulus(bytes, cbModulus); 
            modulus.ToList().ForEach(i => Console.Write(i.ToString() + " "));   

Но гораздо более интересная вещь, которую мы замечаем, это то, что показатель степени на самом деле предшествует модулю, а это означает, что я неправильно его прочитал. Кроме того, здесь следует отметить еще одну вещь: на данный момент у нас есть 16 байтов для определенных полей + 3 байта для экспоненты и 256 байтов для модуля, что дает нам только 275 байтов, в то время как длина нашего открытого ключа составляет 283 байта, что означает, что мы имеем 8 байт дополнительно. Основываясь на здравом смысле, мы можем видеть, что есть 8 байтов нулей, пока мы не доберемся до последних 259 байтов, где первые 3 байта равны 01 00 01, также известному как 65 537 в десятичном формате, что является общей экспонентой для RSA.

        private static byte[] ExtractModulus(byte[] keyBlob, int modulusLength)
        {
            int startIndex = keyBlob.Length - modulusLength; 
            return keyBlob.Skip(startIndex).Take(modulusLength).ToArray();
        }

        private static byte[] ExtractExponent(byte[] keyBlob, int exponentLength, int modulusLength)
        {
            int startIndex = keyBlob.Length - exponentLength - modulusLength;
            return keyBlob.Skip(startIndex).Take(exponentLength).ToArray();
        }

Так вот это да? Нет, теперь я со 100% точностью знаю, что представляет собой каждый байт открытого ключа, но даже после внесения этих изменений у меня все еще остается та же проблема, что и в первом абзаце. У кого-нибудь есть идеи?

ОТ: внедрение закрытого ключа в мою программу на C. Это нормально для обучения. Это совершенно небезопасно для любого реального использования. Если у вас есть реальная необходимость зашифровать данные, чтобы сохранить их конфиденциальность, помещение закрытого ключа в исходный код означает, что каждая система контроля версий, каждая резервная копия файловой системы, каждый временный объектный файл, созданный во время компиляции, каждый исполняемый файл, сгенерированный из этого кода, каждое ядро созданный файл, каждый диск, на который был перенесен двоичный файл, и во многих других местах будет этот закрытый ключ, который каждый сможет найти и использовать.

Andrew Henle 25.03.2024 23:26

@AndrewHenle Да, это хороший момент, я собирался поработать над его безопасностью после того, как все заработает.

UnSure 25.03.2024 23:30

@PresidentJamesK.Polk Нет, это касалось более ранней проблемы преобразования ключей в форму, которую может прочитать каждый алгоритм, но я уже решил это (прочитайте мой раздел редактирования, если хотите дважды проверить), но новая проблема, с которой я столкнулся, изложена в первой пункт заключается в том, что даже при наличии правильных ключей расшифровать сообщение все равно не удастся.

UnSure 26.03.2024 04:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
181
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ошибка обнаруживается, если внимательно посмотреть на определения BCRYPT_RSAKEY_BLOB и BCRYPT_RSAPUBLIC_BLOB:

BCRYPT_RSAKEY_BLOB определяется здесь и выглядит следующим образом:

typedef struct _BCRYPT_RSAKEY_BLOB {
  ULONG Magic;
  ULONG BitLength;
  ULONG cbPublicExp;
  ULONG cbModulus;
  ULONG cbPrime1;
  ULONG cbPrime2;
} BCRYPT_RSAKEY_BLOB;

Опубликованный вами открытый ключ представляет собой BCRYPT_RSAPUBLIC_BLOB структуру, которая определена здесь и выглядит следующим образом:

BCRYPT_RSAKEY_BLOB
PublicExponent[cbPublicExp] // Big-endian.
Modulus[cbModulus] // Big-endian.

Обратите внимание, что все числа, следующие за структурой BCRYPT_RSAKEY_BLOB, имеют обратный порядок.
Однако в вашем коде C# вы инвертируете показатель степени и модуль, что неверно и является основной причиной проблемы.
В вызове ExtractExponent() также есть опечатка (отсутствует третий параметр).

Исправить:

private static RSAParameters ConvertHexToRSAParameters(string hexString)
{
    byte[] bytes = HexStringToByteArray(hexString);
    RSAParameters rsaParams = new RSAParameters();

    // Extract the modulus and exponent from the blob
    byte[] exponent = ExtractExponent(bytes, 3, 256);   // Typo: 3rd param missing!
    byte[] modulus = ExtractModulus(bytes, 256);
                                                        // Fix: Remove the inversion of the arrays! 
    rsaParams.Exponent = exponent;
    rsaParams.Modulus = modulus;

    return rsaParams;
}

Тест: использование опубликованного вами открытого ключа и открытого текста. Быстрая коричневая лиса перепрыгивает через ленивую собаку, возвращается фиксированный код C#, например. следующий зашифрованный текст:

0x89, 0x7a, 0xda, 0x6d, 0x9e, 0x85, 0xc2, 0x1d, 0x76, 0xd6, 0xe2, 0x02, 0xfd, 0x31, 0x7c, 0x7b, 0x56, 0x02, 0x5f, 0x42, 0xd1, 0xb6, 0xc0, 0x36, 0x9f, 0xb3, 0xdc, 0x65, 0x7d, 0x89, 0x4a, 0x05, 0xe8, 0x4e, 0xbd, 0xb1, 0x04, 0x64, 0x80, 0x5c, 0xc3, 0x66, 0x72, 0x98, 0xd5, 0x7f, 0xeb, 0x23, 0x38, 0x61, 0x3d, 0x5c, 0xbd, 0x17, 0xfa, 0x48, 0xfc, 0x75, 0x0e, 0x5b, 0x68, 0x00, 0xae, 0xfb, 0xa9, 0x3d, 0x77, 0x3d, 0xe6, 0x97, 0xca, 0x77, 0x26, 0x2d, 0x8c, 0x9a, 0xfb, 0x2e, 0x52, 0x86, 0x23, 0xff, 0xd6, 0xc6, 0x26, 0x9c, 0x57, 0xd1, 0x54, 0xb8, 0xd1, 0xb0, 0x93, 0x72, 0xd9, 0xf5, 0xd5, 0x15, 0x6a, 0xf3, 0xcd, 0x34, 0xe3, 0x80, 0x79, 0x5f, 0x11, 0x29, 0x4a, 0x7e, 0xce, 0x2f, 0xf7, 0x69, 0x0d, 0xc4, 0xbb, 0x2f, 0x02, 0x1c, 0xf1, 0xf5, 0x75, 0x9c, 0x7c, 0x85, 0x62, 0x9c, 0x3b, 0x54, 0x51, 0x8f, 0xc3, 0xbf, 0xaf, 0xaa, 0xce, 0x6f, 0xdc, 0x6f, 0x74, 0x8d, 0x18, 0xf8, 0x3f, 0x87, 0x10, 0x6e, 0xcb, 0xd7, 0x30, 0x04, 0x20, 0xd7, 0x30, 0xf0, 0x34, 0xae, 0x8f, 0x2e, 0x81, 0x78, 0x67, 0x02, 0x4b, 0x79, 0x5c, 0x8c, 0x81, 0x86, 0x68, 0xca, 0xaa, 0xde, 0x40, 0x41, 0x50, 0x8d, 0xcf, 0x25, 0x8e, 0x72, 0xd0, 0x2f, 0xff, 0x99, 0x5d, 0x86, 0x8c, 0x8b, 0x11, 0x25, 0xd3, 0x70, 0x27, 0xae, 0xf1, 0x35, 0x4f, 0x23, 0x72, 0x0b, 0xb6, 0x04, 0x34, 0xe2, 0x0e, 0xa9, 0x0f, 0xdd, 0x7c, 0x8a, 0x18, 0x6f, 0x40, 0xd7, 0xba, 0xc1, 0x35, 0x68, 0x48, 0xeb, 0xd8, 0x78, 0x77, 0xad, 0x96, 0xb6, 0x83, 0xee, 0xd5, 0xd4, 0xbc, 0x4b, 0x5c, 0x68, 0x07, 0xa3, 0xad, 0x00, 0x22, 0xc3, 0xd9, 0x0f, 0xea, 0x8b, 0x28, 0x54, 0xdb, 0x01, 0x49, 0xfb, 0x1e, 0xde, 0x4f, 0xc7

который можно успешно расшифровать с помощью кода C и опубликованного закрытого ключа.


Примечание о том, что... это означает, что у нас есть дополнительные 8 байт. Основываясь на здравом смысле, мы можем видеть, что имеется 8 байт нулей...: Эти последовательности 0x00 обусловлены cbPrime1 и cbPrime2, которые не включаются в случае открытого ключа, т. е. имеют длину 0 (что из-за ULONG приводит к 4 байтам, каждый из которых состоит из значений 0x00)

Ничто здесь не отличается от того, что я уже сделал. В моем исходном посте я пытался зашифровать все 4 возможности порядка байтов, включая обработку всего как с прямым порядком байтов (хотя показатель степени одинаков как в записи с прямым, так и с прямым порядком байтов). Во-вторых, я использую 3 параметра: код, который вы смотрели, был кодом над разделом Edit. В общем, все, что вы сделали, я уже пробовал, но все равно не работает, потому что у меня нет проблем с использованием библиотеки C#. Мой вопрос касается использования библиотеки C# RSA с BCrypt.

UnSure 26.03.2024 19:31

@UnSure — на моей машине код C (т. е. код BCrypt) может успешно расшифровать зашифрованный текст, сгенерированный с помощью кода C# (с описанными изменениями)! Разве вы не этого хотите, или я неправильно понимаю вопрос?

Topaco 26.03.2024 19:51

не могли бы вы обновить свой ответ, включив в него именно ту строку, которую вы ввели, например EncryptDataWithPublicKey("The quick brown fox jumps over the lazy dog,", i), а затем также не могли бы сообщить мне, какой тип заполнения вы использовали, независимо от того, установлено ли для var encryptedData = rsa.Encrypt(dataToEncryptBytes, true) значение true или false. Я спрашиваю об этом, потому что, хотя кажется, что наш код один и тот же, я получаю разные шестнадцатеричные значения.

UnSure 26.03.2024 19:56

@UnSure — Вы можете запустить мой код C# на .NET Fiddle dotnetfiddle.net/iORfBt. В конце вы найдете закомментированный код C/BCrypt, который я использую (с адаптациями для моей среды, но это не должно быть критично), включая выходные данные.

Topaco 26.03.2024 20:18

Ну, я имею в виду, что он создал другой шестнадцатеричный вывод для той же строки, что является нормальным, и наш код работает точно так же, но я все еще не могу расшифровать его с помощью BCrypt, не столкнувшись с проблемами, описанными в первом абзаце. Какие параметры заполнения вы использовали внутри BCrypt, чтобы заставить его работать?

UnSure 26.03.2024 20:26

@UnSure - я опубликовал свой код C/BCrypt (вы можете найти его в ссылке на скрипт .NET в конце, закомментированный), по сути, это ваш код, т. е. я ничего не менял в отношении заполнения, поэтому PKCS # 1 Применяется версия 1.5 (как в коде C#).

Topaco 26.03.2024 20:35

Другие вопросы по теме