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



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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值