java c++通用DES/AES对称加密算法(包含源代码)
教程分享
>
Java教程
(2666)
2024-04-17 12:33:21
一说明
本来觉得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);
}
https://www.leftso.com/article/273.html