Java与CSP数据兼容之三:Java兼容CSP的DES/3DES密钥数据和密文

  在数据安全应用中,往往需要将客户端由C++调用CryptoAPI生成的密文、传至服务器端的Java代码解密,当然也有相反的操作。这时就需要清楚Windows CryptoAPI和Java安全库之间的差异,以便密钥数据和密文数据格式能兼容。下面将介绍该过程中的几个注意事项。

一、密钥数据块的解析

  在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++代码输出结果如下图:


Java解密代码:

/**
 * 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代码输出结果如下图:

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值