1

I have encrypted a string with RSA public key using the Windows Crypto API, but I'm not able to decrypt it using the RSA private key with Go. The following code is the encryption using C++

HCRYPTPROV hCryptProv = NULL;
HCRYPTKEY hKey = NULL;
DWORD dwPublicKeyLen = 0;
DWORD dwDataLen = 0;
DWORD dwEncryptedLen = 0;
BYTE* pbPublicKey = NULL;
BYTE* pbData = NULL;
HANDLE hPublicKeyFile = NULL;
HANDLE hEncryptedFile = NULL;
HANDLE hPlainFile = NULL;
DWORD lpNumberOfBytesWritten = 0;

BYTE derPubKey[2048];
DWORD derPubKeyLen = 2048;
CERT_PUBLIC_KEY_INFO* publicKeyInfo = NULL;
DWORD publicKeyInfoLen = 0;
HANDLE hFile = NULL;   

if (!CryptAcquireContextW(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{                      
    printf("CryptAcquireContext error 0x%x\n", GetLastError());
    return 1;       
}
   
std::string pempubkeyS = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNoGP0DHZA9RyZlQETr3NGr6hoxn9oHFiFeJwCUooz+qP38vQ53Cs7VtisfEl/FmkwRCz9l0bKU9MZ00Z1/WLTa+48dqGMNL2+um1za0Z0fyxXmYEwy3zvFaswtgzHfXlN+pcay6DsaBXXSvQpC6sz50DvcIw4YsMPqSSBk++LSQIDAQAB";
std::wstring pempubkey = std::wstring(pempubkeyS.begin(), pempubkeyS.end());
    
if (!CryptStringToBinaryW(pempubkey.c_str(), 0, CRYPT_STRING_BASE64, derPubKey, &derPubKeyLen, NULL, NULL))
{
    printf("CryptStringToBinary failed. Err: %d\n", GetLastError());
    return -1;
}

/*
 * Decode from DER format to CERT_PUBLIC_KEY_INFO
 */        
if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_PUBLIC_KEY_INFO, derPubKey, derPubKeyLen, CRYPT_ENCODE_ALLOC_FLAG, NULL, &publicKeyInfo, &publicKeyInfoLen))
{
    printf("CryptDecodeObjectEx 1 failed. Err: %x\n", GetLastError());
    return -1;
}

/*
 * Import the public key using the context
 */
if (!CryptImportPublicKeyInfo(hCryptProv, X509_ASN_ENCODING, publicKeyInfo, &hKey))
{
    printf("CryptImportPublicKeyInfo failed. error: %d\n", GetLastError());
    return -1;
}

LocalFree(publicKeyInfo);

std::string cleartext = "This is a test!";
int len = cleartext.length();
    
// Get lenght for encrypted data
if (!CryptEncrypt(hKey, NULL, TRUE, CRYPT_OAEP, NULL, &dwEncryptedLen, 0))
{
    // Error
    printf("CryptEncrypt error 0x%x\n", GetLastError());
    return 1;
}
cleartext.resize(len + dwEncryptedLen);

// Encrypt data
if (!CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE*)&cleartext[0], &dwDataLen, dwEncryptedLen))
{
    // Error
    printf("CryptEncrypt error 0x%x\n", GetLastError());
    return 1;
}

std::string cleartextbase64 = "";
cleartext.resize(dwDataLen);
Base64Encode(cleartext, &cleartextbase64);
std::cout << cleartextbase64 << std::endl; 
// HkCebflDgmxJ33hmLTrqGsPyyyPKP74MePHedzrB8jiI6AOJhIw06WD93HggIRCgm/A6CqRYYgHe749Z6uTAqsh2dY9bvGMfNGLAQ7g5YFYlK+MWUEyFB1yRH6cDJKloP+J1UhIibGa3R+cwY9EHfDNCZfeTL0zYPHFKun9OBZ0=   

The following code is the decryption using Go

keyRaw, err := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWHdJQkFBS0JnUUROb0dQMERIWkE5UnlabFFFVHIzTkdyNmhveG45b0hGaUZlSndDVW9veitxUDM4dlE1CjNDczdWdGlzZkVsL0Zta3dSQ3o5bDBiS1U5TVowMFoxL1dMVGErNDhkcUdNTkwyK3VtMXphMFowZnl4WG1ZRXcKeTN6dkZhc3d0Z3pIZlhsTitwY2F5NkRzYUJYWFN2UXBDNnN6NTBEdmNJdzRZc01QcVNTQmsrK0xTUUlEQVFBQgpBb0dCQUtia3J6dTlnWjFuVkRjelFSU0JLc2NNZTF2UEFFbTMrQUVjeTBMM1MwUzFBYkNWZUxRZGh0azZ1OUlECmJvTy81TkJRQlZRdUhEN0xtbU16bjlUVVBBaDRRWUxSUGxVQWFRVTM5eThxTDdIbWdoR0lRN0JrZWxPS3hYQjkKNEtBUTFiRksxZ2hwZEFEeDhtTVh5SWZjTG5JSnRJT1ZibWloRjNxdUt4UzBuRlNCQWtFQTE2TlRPeWpKWW9aUQpoaG9XSFJHcXlDU1ZKbGRwaUxSaENxNFAwRVNER2NaTFRlMVVSVXRhRWlZeHk4cGdoQmkxVHpkU1ZVM3h2bk10ClZOeEtoc0tHblFKQkFQUWRXUzdXYUFvWHJYODZ1alJNd1VxcW4zcVJwMWVGM29hdzZ0TWtWZHpDcTZoRzlyRjUKU2pzekhjUkJ4WlNyeklWeXN1T2pBYS9ta0JtbmZtV3Y0WjBDUVFDMy9DMXVvMzA0S0J1YVg3V1FkZHQrU3VCTApSM2ZPNFFDUGFUWXEzOW52NnVXamhxUkpQMktKYTdjL0J0eFV1UFF4czZUM0RicitZUzFEWTNYZkJ5aHRBa0VBCjZwZ05yYkpFZDNaN3VDb3k2YkhkaTZqZTdBWnZuKys1Z3cwZ0RscjczTlNEN0lxTjVzNGQ1VGhoWWNxbld4R2kKMFpnQmpEdUprb1pyY3d3QXJ5NVFEUUpCQUt0Uk1LbmJnZFR3TWRQbTMwQWErQk50UGNwckN4VXFFWHpCT2lZSQpXVVBZOTBCdmxGSzkvK3VVWktOQzhoK1Ntc2F3ekFaKzZUVzlEWUFuSWZ1UkExZz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K")
if err != nil {
    panic(err)
}

block, _ := pem.Decode(keyRaw)
configPrivateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
    panic(err)
}
encodedEncrypted := `HkCebflDgmxJ33hmLTrqGsPyyyPKP74MePHedzrB8jiI6AOJhIw06WD93HggIRCgm/A6CqRYYgHe749Z6uTAqsh2dY9bvGMfNGLAQ7g5YFYlK+MWUEyFB1yRH6cDJKloP+J1UhIibGa3R+cwY9EHfDNCZfeTL0zYPHFKun9OBZ0=`
cipherText, err := base64.StdEncoding.DecodeString(encodedEncrypted)
if err != nil {
    panic(err)
}

cleartextMessage, err := rsa.DecryptPKCS1v15(rand.Reader, configPrivateKey, cipherText)
//cleartextMessage, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, configPrivateKey, cipherText, nil)
if  err != nil {
    panic(err) // panic: crypto/rsa: decryption error
}
fmt.Println(string(cleartextMessage))

As you can see I've got a panic: crypto/rsa: decryption error. Any ideas? Thanks in advance!

Topaco
  • 40,594
  • 4
  • 35
  • 62
Tmoto8
  • 13
  • 3

1 Answers1

1

There are the following issues:

  • CryptEncrypt() returns the result in little endian format, so in the Go code the ciphertext must be reversed, e.g. (see here):

    ...
    for i, j := 0, len(cipherText)-1; i < j; i, j = i+1, j-1 {
        cipherText[i], cipherText[j] = cipherText[j], cipherText[i]
    }
    ...
    
  • On encryption (2nd CryptEncrypt() call) the 6th parameter is &dwDataLen, where dwDataLen is 0. According to the documentation, the 6th parameter contains on entry the plaintext length (and on exit the ciphertext length). Because here the 6th parameter on entry is 0, an empty string is encrypted. This is probably not intended and must be fixed accordingly (i.e. dwDataLen must be set to the plaintext length)!

  • On encryption (2nd CryptEncrypt() call) the 4th parameter is 0, so PKCS#1 v1.5 padding is applied. Therefore, in the Go code, rsa.DecryptPKCS1v15() must be used, which is currently the case (rsa.DecryptOAEP() is commented out). Note that when determining the length of the encrypted data (1st CryptEncrypt() call), the CRYPT_OAEP flag is set, which is inconsistent, but probably has no effect. This point has already been addressed in the comments.

If the ciphertext is reversed, the decryption works and returns an empty string as result because of dwDataLen equals 0.

Topaco
  • 40,594
  • 4
  • 35
  • 62