RSA Decryption from c++ Windows Crypto API to Golang

huangapple go评论93阅读模式
英文:

RSA Decryption from c++ Windows Crypto API to Golang

问题

我已经使用Windows Crypto API使用RSA公钥对字符串进行了加密,但是我无法使用Go的RSA私钥对其进行解密。
以下是使用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=

以下是使用Go进行解密的代码:

keyRaw, err := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWHdJQkFBS0JnUUROb0dQMERIWkE5UnlabFFFVHIzTkdyNmhveG45b0hGaUZlSndDVW9veitxUDM4dlE1CjNDczdWdGlzZkVsL0Zta3dSQ3o5bDBiS1U5TVowMFoxL1dMVGErNDhkcUdNTkwyK3VtMXphMFowZnl4WG1ZRXcKeTN6dkZhc3d0Z3pIZlhsTitwY2F5NkRzYUJYWFN2UXBDNnN6NTBEdmNJdzRZc01QcVNTQmsrK0xTUUlEQVFBQgpBb0dCQUtia3J6dTlnWjFuVkRjelFSU0JLc2NNZTF2UEFFbTMrQUVjeTBMM1MwUzFBYkNWZUxRZGh0azZ6CkxhR3hKM

<details>
<summary>英文:</summary>

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

&lt;!-- language: lang-cpp --&gt;
    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(&amp;hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    {                      
        printf(&quot;CryptAcquireContext error 0x%x\n&quot;, GetLastError());
        return 1;       
    }
       
    std::string pempubkeyS = &quot;MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNoGP0DHZA9RyZlQETr3NGr6hoxn9oHFiFeJwCUooz+qP38vQ53Cs7VtisfEl/FmkwRCz9l0bKU9MZ00Z1/WLTa+48dqGMNL2+um1za0Z0fyxXmYEwy3zvFaswtgzHfXlN+pcay6DsaBXXSvQpC6sz50DvcIw4YsMPqSSBk++LSQIDAQAB&quot;;
    std::wstring pempubkey = std::wstring(pempubkeyS.begin(), pempubkeyS.end());
        
    if (!CryptStringToBinaryW(pempubkey.c_str(), 0, CRYPT_STRING_BASE64, derPubKey, &amp;derPubKeyLen, NULL, NULL))
    {
        printf(&quot;CryptStringToBinary failed. Err: %d\n&quot;, 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, &amp;publicKeyInfo, &amp;publicKeyInfoLen))
    {
        printf(&quot;CryptDecodeObjectEx 1 failed. Err: %x\n&quot;, GetLastError());
        return -1;
    }

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

    LocalFree(publicKeyInfo);

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

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

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


The following code is the decryption using Go

&lt;!-- language: lang-go --&gt;
    keyRaw, err := base64.StdEncoding.DecodeString(&quot;LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWHdJQkFBS0JnUUROb0dQMERIWkE5UnlabFFFVHIzTkdyNmhveG45b0hGaUZlSndDVW9veitxUDM4dlE1CjNDczdWdGlzZkVsL0Zta3dSQ3o5bDBiS1U5TVowMFoxL1dMVGErNDhkcUdNTkwyK3VtMXphMFowZnl4WG1ZRXcKeTN6dkZhc3d0Z3pIZlhsTitwY2F5NkRzYUJYWFN2UXBDNnN6NTBEdmNJdzRZc01QcVNTQmsrK0xTUUlEQVFBQgpBb0dCQUtia3J6dTlnWjFuVkRjelFSU0JLc2NNZTF2UEFFbTMrQUVjeTBMM1MwUzFBYkNWZUxRZGh0azZ1OUlECmJvTy81TkJRQlZRdUhEN0xtbU16bjlUVVBBaDRRWUxSUGxVQWFRVTM5eThxTDdIbWdoR0lRN0JrZWxPS3hYQjkKNEtBUTFiRksxZ2hwZEFEeDhtTVh5SWZjTG5JSnRJT1ZibWloRjNxdUt4UzBuRlNCQWtFQTE2TlRPeWpKWW9aUQpoaG9XSFJHcXlDU1ZKbGRwaUxSaENxNFAwRVNER2NaTFRlMVVSVXRhRWlZeHk4cGdoQmkxVHpkU1ZVM3h2bk10ClZOeEtoc0tHblFKQkFQUWRXUzdXYUFvWHJYODZ1alJNd1VxcW4zcVJwMWVGM29hdzZ0TWtWZHpDcTZoRzlyRjUKU2pzekhjUkJ4WlNyeklWeXN1T2pBYS9ta0JtbmZtV3Y0WjBDUVFDMy9DMXVvMzA0S0J1YVg3V1FkZHQrU3VCTApSM2ZPNFFDUGFUWXEzOW52NnVXamhxUkpQMktKYTdjL0J0eFV1UFF4czZUM0RicitZUzFEWTNYZkJ5aHRBa0VBCjZwZ05yYkpFZDNaN3VDb3k2YkhkaTZqZTdBWnZuKys1Z3cwZ0RscjczTlNEN0lxTjVzNGQ1VGhoWWNxbld4R2kKMFpnQmpEdUprb1pyY3d3QXJ5NVFEUUpCQUt0Uk1LbmJnZFR3TWRQbTMwQWErQk50UGNwckN4VXFFWHpCT2lZSQpXVVBZOTBCdmxGSzkvK3VVWktOQzhoK1Ntc2F3ekFaKzZUVzlEWUFuSWZ1UkExZz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K&quot;)
	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&#39;ve got a *panic: crypto/rsa: decryption error*. Any ideas? Thanks in advance!

</details>


# 答案1
**得分**: 1

有以下问题

- [`CryptEncrypt()`][2] 返回结果是以 _little_ endian 格式所以在 Go 代码中密文必须被反转例如参见[这里][1]):

  ```go
  ...
  for i, j := 0, len(cipherText)-1; i < j; i, j = i+1, j-1 {
	  cipherText[i], cipherText[j] = cipherText[j], cipherText[i]
  }
  ...
  • 在加密(第二个 CryptEncrypt() 调用)时,第六个参数是 &dwDataLen,其中 dwDataLen0。根据文档,第六个参数在输入时包含明文长度(在输出时包含密文长度)。因为这里第六个参数在输入时是 0,所以加密的是一个 字符串。这可能不是预期的结果,需要相应地修复(即将 dwDataLen 设置为明文长度)!

  • 在加密(第二个 CryptEncrypt() 调用)时,第四个参数是 0,因此应用了 PKCS#1 v1.5 填充。因此,在 Go 代码中,应使用 rsa.DecryptPKCS1v15(),目前已经这样做了(rsa.DecryptOAEP() 被注释掉了)。请注意,在确定加密数据的长度时(第一个 CryptEncrypt() 调用),设置了 CRYPT_OAEP 标志,这是不一致的,但可能没有影响。这一点已经在注释中提到过。

如果密文被反转,解密将正常工作,并返回一个空字符串作为结果,因为 dwDataLen 等于 0

英文:

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 &lt; j; i, j = i+1, j-1 {
        cipherText[i], cipherText[j] = cipherText[j], cipherText[i]
    }
    ...
    
  • On encryption (2nd CryptEncrypt() call) the 6th parameter is &amp;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.

huangapple
  • 本文由 发表于 2022年2月6日 03:41:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/71001533.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定