从 C++ Windows Crypto API 到 Golang 的 RSA 解密

RSA Decryption from c++ Windows Crypto API to Golang

我已经使用 Windows 加密 API 使用 RSA public 密钥加密了一个字符串,但我无法使用带有 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("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))

如您所见,我遇到了 恐慌:crypto/rsa:解密错误。有任何想法吗?提前致谢!

存在以下问题:

  • 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]
    }
    ...
    
  • 关于加密(第 2 次 CryptEncrypt() 调用),第 6 个参数是 &dwDataLen,其中 dwDataLen0。根据文档,第 6 个参数在输入时包含明文长度(在退出时包含密文长度)。因为这里第 6 个参数 on entry0,所以加密了一个 empty 字符串。这可能不是故意的,必须相应地修复(即 dwDataLen 必须设置为明文长度)!

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

如果密文被反转,则解密有效,并且 returns 结果为空字符串,因为 dwDataLen 等于 0