一、密钥数据块的解析
在Windows平台上,C++代码调用CryptoAPI函数CryptExportKey()得到的对称密钥数据结构为PLAINTEXTKEYBLOB类型,该数据块的结构为:“BLOBHEADER + 密钥数据长度 + 密钥数据“,其中”密钥数据长度“为4个字节。在Java代码中,需要解析该数据库,得到密钥数据,用来创建对称密钥对象。
二、加密模式保持一致
对称密钥加密,常用的有ECB和CBC两种模式,CryptoAPI函数CryptEncrypt()用来使用密钥加密数据时,如果没有对使用的密钥做模式设置,则默认使用CBC模式,MSDN中的介绍如下:
而Java的安全库中,默认时使用的ECB模式。所以需要保证两边使用的是同一种加密模式,需要通过设置来完成。可以通过C++代码调用CryptSetKeyParam()来设置CryptoAPI的加密模式,也可以在Java中指定明确的加密模式。详细代码见后面的例程。
三、加密参数保持一致
如果使用CBC模式加密,这需要指定加密时使用的向量IV。在使用CryptoAPI时,如果没有明确指定IV的值,则默认使用8个0作为IV。MSDN中的说明如下:
四、密文填充模式保持一致
由于加密时数据不一定为密钥密文块的整数倍,所以往往需要对密文进行补齐。CryptoAPI只支持PKCS5的补齐方式,所以在Java中也需要使用PKCS5补齐模式。关于CryptoAPI的补齐说明,MSDN说明如下:
五、加密文本时使用UTF-8编码
由于Java使用的UTF-8字符编码,所以在对文本字符加密时,C++代码需要将字符串需要按UTF-8字符集转化为BYTE[]数组,再进行加密,否则Java端解密成功后,显示文本时中文(或者其他多字节字符)时会是乱码。当然,如果只是对二进制数据加解密,则无需考虑这点。
基于以上说明过,完整地C++加密、Java解密的代码如下:
C++加密代码:
#include "stdafx.h"
#include <windows.h>
#include <atlconv.h>
#include "..\Common\Base64.h"
void Reverse(LPBYTE lpData, DWORD dwLen);
void PrintDataInHex(LPBYTE lpData, DWORD dwLen);
void PrintDataInBase64(LPBYTE lpData, DWORD dwLen);
int _tmain(int argc, _TCHAR* argv[])
{
ULONG ulRes = 0;
ULONG ulKeyLen = 0;
ULONG ulPlainDataLen = 0;
ULONG ulCipherLen = 256;
LPBYTE lpbtKeyData = NULL;
LPBYTE lpbtPlainData = NULL;
BYTE btCipherData[256] = {0};
HCRYPTPROV hProv = NULL;
HCRYPTKEY hKey = NULL;
const TCHAR tcCSPName[] = _T("Microsoft Enhanced Cryptographic Provider v1.0");
const TCHAR tcPlainText[] = _T("This is plian text!这是明文数据!");
// 打开CSP
if (!CryptAcquireContext(&hProv, NULL, tcCSPName, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
ulRes = GetLastError();
printf("CryptAcquireContext() failed! ulRes = 0x%x\n", ulRes);
goto END;
}
// 生成3DES密钥
if (!CryptGenKey(hProv, CALG_3DES, 0x00A80000|CRYPT_EXPORTABLE, &hKey) || !hKey)
{
ulRes = GetLastError();
printf("CryptGenKey() failed! ulRes = 0x%x\n", ulRes);
goto END;
}
// 导出3DES密钥数据(明文)
if (!CryptExportKey(hKey, NULL, PLAINTEXTKEYBLOB, NULL, NULL, &ulKeyLen) || ulKeyLen == 0)
{
ulRes = GetLastError();
printf("CryptExportKey() failed! ulRes = 0x%x\n", ulRes);
goto END;
}
lpbtKeyData = new BYTE[ulKeyLen];
if (!CryptExportKey(hKey, NULL, PLAINTEXTKEYBLOB, NULL, lpbtKeyData, &ulKeyLen))
{
printf("CryptExportKey() failed! ulRes = 0x%x\n", ulRes);
goto END;
}
printf("3DES Key data:\n");
PrintDataInHex(lpbtKeyData, ulKeyLen);
printf("\n");
PrintDataInBase64(lpbtKeyData, ulKeyLen);
// 原文数据
ULONG ulLen = WideCharToMultiByte(CP_UTF8, 0, tcPlainText, wcslen(tcPlainText), NULL, 0, NULL, NULL);
lpbtPlainData = new BYTE[ulLen + 1];
memset(lpbtPlainData, 0, ulLen + 1);
ulLen = WideCharToMultiByte(CP_UTF8, 0, tcPlainText, wcslen(tcPlainText), (LPSTR)lpbtPlainData, ulLen+1, NULL, NULL);
ulPlainDataLen = ulLen;
printf("\nPlain text data:\n");
PrintDataInHex(lpbtPlainData, ulPlainDataLen);
// 加密数据
ulCipherLen = ulPlainDataLen;
memcpy(btCipherData, lpbtPlainData, ulPlainDataLen);
if (!CryptEncrypt(hKey, NULL, TRUE, 0, (LPBYTE)btCipherData, &ulCipherLen, 256))
{
ulRes = GetLastError();
printf("CryptEncrypt() failed! ulRes = 0x%x\n", ulRes);
goto END;
}
printf("\nCipher data:\n");
PrintDataInHex(btCipherData, ulCipherLen);
printf("\n");
PrintDataInBase64(btCipherData, ulCipherLen);
END:
if (lpbtKeyData)
{
delete []lpbtKeyData;
lpbtKeyData = NULL;
}
if (hKey)
{
CryptDestroyKey(hKey);
hKey = NULL;
}
if (hProv)
{
CryptReleaseContext(hProv, 0);
hProv = NULL;
}
printf("\n");
getchar();
return 0;
}
void Reverse(LPBYTE lpData, DWORD dwLen)
{
BYTE temp = 0;
for (ULONG i = 0; i < dwLen / 2; i++)
{
temp = lpData[i];
lpData[i] = lpData[dwLen - (i + 1)];
lpData[dwLen - ( i + 1)] = temp;
}
}
void PrintDataInHex(LPBYTE lpData, DWORD dwLen)
{
for (DWORD dwIndex = 0; dwIndex < dwLen; dwIndex++)
{
printf("%02X ", lpData[dwIndex]);
if ((dwIndex + 1) % 16 == 0)
{
printf("\n");
}
}
}
void PrintDataInBase64(LPBYTE lpData, DWORD dwLen)
{
ULONG ulBase64Len = 0;
LPTSTR lptsBase64 = NULL;
ulBase64Len = BinaryToBase64(lpData, dwLen, NULL);
if (0 == ulBase64Len)
{
printf("BinaryToBase64() failed!\n");
return;
}
lptsBase64 = new TCHAR[ulBase64Len + 1];
ulBase64Len = BinaryToBase64(lpData, dwLen, lptsBase64);
if (0 == ulBase64Len)
{
printf("BinaryToBase64() failed!\n");
goto END;
}
_tprintf(_T("%s\n"), lptsBase64);
END:
if (lptsBase64)
{
delete []lptsBase64;
lptsBase64 = NULL;
}
}
以上C++代码输出结果如下图:
/**
* Created by Singler on 2015/10/29.
* DES/3DES加解密实现类
*
*/
public class DES {
static ALG _keyAlg = ALG.ALG_DES;
final static byte[] _IV = {0, 0, 0, 0, 0, 0, 0, 0};
enum ALG {ALG_DES, ALG_3DES};
enum MODE {ECB, CBC, CFB, OFB, CTS};
/**
* 创建一个DES/3DES对称密钥对象
*
* */
public static SecretKey CreateKey(byte[] keyData, ALG alg) {
SecretKey key = null;
String algName = "DES";
try {
/** 生成密钥对象 */
if (ALG.ALG_DES == alg) {
algName = "DES";
_keyAlg = ALG.ALG_DES;
}
else if (ALG.ALG_3DES == alg) {
algName = "DESede";
_keyAlg = ALG.ALG_3DES;
}
else {
return null;
}
key = new SecretKeySpec(keyData, algName);
} catch(java.lang.Exception e3){
e3.printStackTrace();
}
return key;
}
/**
* 使用一个DES/3DES密钥解密数据
*
* */
public static byte[] decrypt(SecretKey key, MODE mode, byte[] src) {
Cipher cipher = null;
IvParameterSpec ivp = new IvParameterSpec(_IV);
try {
/** 根据不同模式,初始化密文对象 */
if (MODE.ECB == mode) {
if (ALG.ALG_3DES == _keyAlg) {
cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
}
else {
cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
}
cipher.init(Cipher.DECRYPT_MODE, key);
}
else if (MODE.CBC == mode) {
if (ALG.ALG_3DES == _keyAlg) {
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
}
else {
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
}
cipher.init(Cipher.DECRYPT_MODE, key, ivp);
}
/** 返回解密结果 */
return cipher.doFinal(src);
} catch (java.security.NoSuchAlgorithmException e1) {
// TODO: handle exception
e1.printStackTrace();
}catch(javax.crypto.NoSuchPaddingException e2){
e2.printStackTrace();
}catch(java.lang.Exception e3){
e3.printStackTrace();
}
return null;
}
}
public class Main {
static final String cipherIn3Des = "rg0RdqDohH4Yiu1Eej6+r2cuYo9ilVPdO/a+VLb+AeXG1J8cyNygF+y42ijJhe6q";
static final String keyDataFor3Des = "CAIAAANmAAAYAAAAyKTvWyz4E+W5aMQ+Q+nHMhPpLzvy9Dtn";
/** CryptoAPI导出的SesssionKey数据块中块头的长度 = sizeof(BLOBHEADER) */
static final int _KeyBlobHeaderSize = 8;
public static void main(String[] args) {
try {
int len = 0;
byte[] lenData = new byte[4];
//从CSP导出的PLAINTEXTKEYBLOB数据块中获取3DES密钥数据
byte[] keyBlobData = Base64.getDecoder().decode(keyDataFor3Des);
System.arraycopy(keyBlobData, _KeyBlobHeaderSize, lenData, 0, 4);
len = Convert.toInt(lenData);
byte[] keyData = new byte[len];
System.arraycopy(keyBlobData, _KeyBlobHeaderSize + 4, keyData, 0, len);
//解密数据
byte[] cipherData = Base64.getDecoder().decode(cipherIn3Des);
SecretKey key = DES.CreateKey(keyData, DES.ALG.ALG_3DES);
byte[] decrypted = DES.decrypt(key, DES.MODE.CBC, cipherData);
//解密后的数据(16进制显示)
System.out.println("Decrypted data:");
String decryptedData = Convert.bytesToHexString(decrypted);
System.out.println(decryptedData);
//解密后的数据(文本显示)
System.out.println("Decrypted text:");
String decryptedStr = new String(decrypted);
System.out.println(decryptedStr);
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
以上Java代码输出结果如下图: