一说明

本来觉得DES、AES这种流行加密算法,使用起来应该很简单。但研究后发现有两个变数:
  • 分块的方式。加密是逐块进行的。分块方法有:CBC、ECB、CFB……
  • padding的方式。当数据的位数不及块的大小时,需要填充。填充方式有:NoPadding、PKCS5Padding……
如果加解密端采用不同的分块方式或padding方式,即使都是采用DES/AES算法,同样无法解密成功。上次需要C端和Java端进行密文传输,就跪在这一点上(那时候没时间研究)。
参考文章:Java AES算法和openssl配对 ,主要通过其了解openssl里比较高级的EVP系列API(其默认padding和java一样都是PKCS5Padding),节约了搜索时间。
贴代码了,以下代码测试通过了。Java和C++均可以正确解密对方的密文。
约定:分块和padding采用Java默认的 ECB + PKCS5Padding。

二 DES加解密

Java端DES加解密

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class DESUtil {
	/** 默认算法格式 **/
	static String default_transformation = "DES/ECB/PKCS5Padding";

	/**
	 * 根据key获取加密器
	 * 
	 * @param keyData
	 *            key 8byte
	 * @return 加密器
	 * @throws Exception
	 *             异常
	 */
	private static Cipher getEncryptCipher(byte[] keyData) throws Exception {
		if (keyData.length != 8) {
			throw new Exception("Key Data Must 8 byte!");
		}
		SecretKeySpec key = new SecretKeySpec(keyData, "DES");
		// 指定分块ECB模式,填充PKCS5Padding模式
		Cipher encryptCipher = Cipher.getInstance(default_transformation);
		// 初始化加密的容器
		encryptCipher.init(Cipher.ENCRYPT_MODE, key);
		return encryptCipher;
	}

	/**
	 * 根据key获取解码器
	 * 
	 * @return 解码器
	 * @throws Exception
	 *             异常
	 */
	private static Cipher getDecryptCipher(byte[] keyData) throws Exception {
		if (keyData.length != 8) {
			throw new Exception("Key Data Must 8 byte!");
		}
		SecretKeySpec key = new SecretKeySpec(keyData, "DES");
		Cipher decryptCipher = Cipher.getInstance(default_transformation);
		// 初始化解密的容器
		decryptCipher.init(Cipher.DECRYPT_MODE, key);
		return decryptCipher;
	}

	/**
	 * DES加密
	 * 
	 * @param data
	 *            待加密数据
	 * @param keyData
	 *            key值
	 * @return 加密后的数据
	 * @throws Exception
	 *             异常
	 */
	public static byte[] encrypt(byte[] data, byte[] keyData) throws Exception {
		return getEncryptCipher(keyData).doFinal(data);
	}

	/**
	 * DES解密
	 * 
	 * @param data
	 *            加密后的数据
	 * 
	 * @param keyData
	 *            key值
	 * @return 解密数据
	 * @throws Exception
	 *             异常
	 */
	public static byte[] decrypt(byte[] data, byte[] keyData) throws Exception {
		return getDecryptCipher(keyData).doFinal(data);
	}

	/**
	 * 测试
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			byte[] data = "测试123456".getBytes();
			byte[] keyData = "12345678".getBytes();
			System.out.println("原文:" + new String(data));
			byte[] enData = encrypt(data, keyData);
			System.out.println("加密后:" + new String(enData));
			byte[] deData = decrypt(enData, keyData);
			System.out.println("解密后:" + new String(deData));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
说明:Java必须指定填充模式和padding模式。DES/ECB/PKCS5Padding,好与C++同步
测试执行结果:
原文:测试123456
加密后:�a���O���?I�]�Y
解密后:测试123456

C++端DES加解密

c++DES加密代码
#include <string>
#include <iostream>
#include <stdio.h>
#include <assert.h>
#include <openssl/objects.h>
#include <openssl/evp.h>

// 注意:参数和返回值全部是二进制数据
std::string desEncrypt(const std::string& source, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_EncryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[source.length() + 64]; // 弄个足够大的空间
    int len1 = 0;
    ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("hellodes", 8);    // 二进制数据,而不是以0结尾的字符串
    // 读取文件内容(简单起见认为文件内容<100K)
    char buf[1024*100];
    FILE* fp = fopen("src.txt", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string source(buf, bytes); // 二进制数据
    // 加密
    std::string enc = desEncrypt(source, key);
    std::cout << "desEncrypt:" << source.length() << "->" << enc.length() << std::endl;
    // 输出到文件
    fp =  fopen("enc.bin", "wb");
    fwrite(enc.data(), 1, enc.length(), fp);
    fclose(fp);
}
c++DES解密
#include <string>
#include <iostream>
#include <stdio.h>
#include <assert.h>
#include <openssl/objects.h>
#include <openssl/evp.h>

// 注意:参数和返回值全部是二进制数据
std::string desDecrypt(const std::string& ciphertext, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_DecryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[ciphertext.length() + 64]; // 弄个足够大的空间
    int len1 = 0;
    ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("hellodes", 8);    // 二进制数据,而不是以0结尾的字符串
    // 读取文件内容(简单起见认为文件内容<100K)
    char buf[1024*100];
    FILE* fp = fopen("enc.bin", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string data(buf, bytes); // 二进制数据
    // 加密
    std::string dec = desDecrypt(data, key);
    std::cout << "desDecrypt:" << data.length() << "->" << dec.length() << std::endl;
    // 输出到文件
    fp =  fopen("dec.txt", "wb");
    fwrite(dec.data(), 1, dec.length(), fp);
    fclose(fp);
}

说明:DES、AES加密算法都是针对数据块,Java加解密函数参数使用byte数组。C++用std::string,那是因为这是C++中使用byte数组的最简单方式(std::string可以存储二进制数据,很多人没想到吧),缺点是拷贝内存的次数可能会略多些。如果想要优化拷贝效率,可以使用自己封装的Buffer类来代替std::string。

三 AES加解密

Java端AES加解密

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

/**
 * DES加解密算法工具类
 * 
 * @author xqlee
 *
 */
public class AESUtil {
	/** 默认算法格式 **/
	static String default_transformation = "AES/ECB/PKCS5Padding";

	/**
	 * 根据key获取加密器
	 * 
	 * @param keyData
	 *            key 8byte
	 * @return 加密器
	 * @throws Exception
	 *             异常
	 */
	private static Cipher getEncryptCipher(byte[] keyData) throws Exception {
		if (keyData.length != 16) {
			throw new Exception("Key Data Must 8 byte!");
		}
		SecretKeySpec key = new SecretKeySpec(keyData, "AES");
		// 指定分块ECB模式,填充PKCS5Padding模式
		Cipher encryptCipher = Cipher.getInstance(default_transformation);
		// 初始化加密的容器
		encryptCipher.init(Cipher.ENCRYPT_MODE, key);
		return encryptCipher;
	}

	/**
	 * 根据key获取解码器
	 * 
	 * @return 解码器
	 * @throws Exception
	 *             异常
	 */
	private static Cipher getDecryptCipher(byte[] keyData) throws Exception {
		if (keyData.length != 16) {
			throw new Exception("Key Data Must 8 byte!");
		}
		SecretKeySpec key = new SecretKeySpec(keyData, "AES");
		Cipher decryptCipher = Cipher.getInstance(default_transformation);
		// 初始化解密的容器
		decryptCipher.init(Cipher.DECRYPT_MODE, key);
		return decryptCipher;
	}

	/**
	 * AES加密
	 * 
	 * @param data
	 *            待加密数据
	 * @param keyData
	 *            key值
	 * @return 加密后的数据
	 * @throws Exception
	 *             异常
	 */
	public static byte[] encrypt(byte[] data, byte[] keyData) throws Exception {
		return getEncryptCipher(keyData).doFinal(data);
	}

	/**
	 * AES解密
	 * 
	 * @param data
	 *            加密后的数据
	 * 
	 * @param keyData
	 *            key值
	 * @return 解密数据
	 * @throws Exception
	 *             异常
	 */
	public static byte[] decrypt(byte[] data, byte[] keyData) throws Exception {
		return getDecryptCipher(keyData).doFinal(data);
	}

	/**
	 * 测试
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			byte[] data = "测试123456".getBytes();
			byte[] keyData = "1234567887654321".getBytes();
			System.out.println("原文:" + new String(data));
			byte[] enData = encrypt(data, keyData);
			System.out.println("加密后:" + new String(enData));
			byte[] deData = decrypt(enData, keyData);
			System.out.println("解密后:" + new String(deData));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

C++端AES加解密

加密:
#include <string>
#include <iostream>
#include <stdio.h>
#include <assert.h>
#include <openssl/objects.h>
#include <openssl/evp.h>
// 注意:参数和返回值全部是二进制数据
std::string aesEncrypt(const std::string& source, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[source.length() + 64]; // 弄个足够大的空间
    int len1 = 0;
    ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("helloaeshelloaes", 16);    // 二进制数据,而不是以0结尾的字符串
    // 读取文件内容(简单起见认为文件内容<100K)
    char buf[1024*100];
    FILE* fp = fopen("src.txt", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string source(buf, bytes); // 二进制数据
    // 加密
    std::string enc = aesEncrypt(source, key);
    std::cout << "aesEncrypt:" << source.length() << "->" << enc.length() << std::endl;
    // 输出到文件
    fp =  fopen("enc.bin", "wb");
    fwrite(enc.data(), 1, enc.length(), fp);
    fclose(fp);
}
解密:
#include <string>
#include <iostream>
#include <stdio.h>
#include <assert.h>
#include <openssl/objects.h>
#include <openssl/evp.h>

// 注意:参数和返回值全部是二进制数据
std::string aesDecrypt(const std::string& ciphertext, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_DecryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[ciphertext.length() + 64]; // 弄个足够大的空间
    int len1 = 0;
    ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("helloaeshelloaes", 16);    // 二进制数据,而不是以0结尾的字符串
    // 读取文件内容(简单起见认为文件内容<100K)
    char buf[1024*100];
    FILE* fp = fopen("enc.bin", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string data(buf, bytes); // 二进制数据
    // 加密
    std::string dec = aesDecrypt(data, key);
    std::cout << "aesDecrypt:" << data.length() << "->" << dec.length() << std::endl;
    // 输出到文件
    fp =  fopen("dec.txt", "wb");
    fwrite(dec.data(), 1, dec.length(), fp);
    fclose(fp);
}
暂无评论