C++ Java和Node.js之间的AES加解密研究(ECB)

现在的软件没个前端后端移动端桌面端都不好意思拿出手。

不同平台之间通信时会遇到同样的数据同样的加密算法,Java、JS、C++、.Net算出来的不一样的抓狂情形。都知道肯定是各加密库的默认算法配置造成的差异,然而咱也不是加密算法专家,各种原理也是越看越晕,还是写代码试验最省心。

下面是各语言AES/ECB/128的加解密代码,经测试互相之间可通用。供参考(注意CTRL+C的话key变量不要忘了改)

Node.js 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
crypto = require('crypto');

/**
* aes加密
* @param data
* @param secretKey
*/
aesEncrypt = function(data, secretKey) {
var cipher = crypto.createCipher('aes-128-ecb',secretKey);
return cipher.update(data,'utf8','base64') + cipher.final('base64');
}

/**
* aes解密
* @param data
* @param secretKey
* @returns {*}
*/
aesDecrypt = function(data, secretKey) {
var cipher = crypto.createDecipher('aes-128-ecb',secretKey);
return cipher.update(data,'base64','utf8') + cipher.final('utf8');
}

const data = "Across the Great Wall we can reach every corner in the world.";
const key = "vwOr2r#%Yv,+A4e[";
let encryptStr = aesEncrypt(data, key);
console.log( encryptStr );
let out = aesDecrypt(encryptStr, key);
console.log(out);

Java/Android 代码

原代码是从这里抄来简化的:https://www.cnblogs.com/libo0125ok/p/7668026.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.example.myapplication;

import java.security.MessageDigest;

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

/**
* AES的加密和解密
* @author libo
*/
public class AES {
//算法
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
public static final String DEFAULT_CODING = "utf-8";

/**
* AES加密
* @param content 待加密的内容
* @param encryptKey 加密密钥
* @return 加密后的byte[]
*/
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
byte[] input = content.getBytes(DEFAULT_CODING);

MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(encryptKey.getBytes(DEFAULT_CODING));
SecretKeySpec skc = new SecretKeySpec(thedigest, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);

return cipherText;
}

/**
* AES解密
* @param encryptBytes 待解密的byte[]
* @param decryptKey 解密密钥
* @return 解密后的String
*/
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
byte[] keyb = decryptKey.getBytes(DEFAULT_CODING);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(keyb);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
Cipher dcipher = Cipher.getInstance("AES");
dcipher.init(Cipher.DECRYPT_MODE, skey);

byte[] clearbyte = dcipher.doFinal(encryptBytes);
return new String(clearbyte);
}
}

在Activity中测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.example.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.util.Base64;

public class MainActivity extends AppCompatActivity {

private static final String key = "vwOr2r#%Yv,+A4e[";

private Button buttonEncrypt;
private Button buttonDecrypt;
private EditText viewInput;
private EditText viewOutput;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonEncrypt = findViewById(R.id.buttonEncrypt);
buttonDecrypt = findViewById(R.id.buttonDecrypt);
viewInput = findViewById(R.id.textViewInput);
viewOutput = findViewById(R.id.textViewOutput);

// 从viewInput的输入框中取得数据,加密,转成base64,显示到viewOutput中
buttonEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
byte[] encrypted_data;
String s = viewInput.getText().toString();
encrypted_data = AES.aesEncryptToBytes(s, key);
String encrypted_data_base64 = Base64.encodeToString(encrypted_data, 0);
viewOutput.setText(encrypted_data_base64);
} catch (Exception e) {
e.printStackTrace();
}
}
});

// 从viewInput的输入框中取得数据,先base64解码,再解密,结果显示到viewOutput中
buttonDecrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
String s = viewInput.getText().toString();
byte[] src_data = Base64.decode(s, 0);
String decrypted_data = AES.aesDecryptByBytes(src_data, key);
viewOutput.setText(decrypted_data);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

C++代码 openssl版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <string>
#include <vector>
#include <openssl/evp.h>
#include <openssl/md5.h>

using namespace std;

string g_data = "Across the Great Wall we can reach every corner in the world.";
string g_key = "vwOr2r#%Yv,+A4e[";

typedef std::vector<uint8_t> byte_array;
std::string base64Encode(byte_array data)
{
if (data.empty()) return {};

std::string res;
res.resize((data.size() / 3 + 1) * 4);
int len = EVP_EncodeBlock((unsigned char *)&res[0], &data[0], data.size());
res.resize(len);
return res;
}

// base64 解码
byte_array base64Decode(std::string data)
{
int len = data.length();
if (len == 0) return {};

byte_array res;
res.resize(len);

len = EVP_DecodeBlock(&res[0], (unsigned char *)&data[0], len);
res.resize(len);
return res;
}

int main()
{
unsigned char key[16] = { 0 };

int inLen = g_data.length();
int encLen = 0;
int outlen = 0;
unsigned char encData[1024];

std::copy(g_key.begin(), g_key.end(), key);
MD5((unsigned char*)g_key.c_str(), g_key.length(), key);

//加密
EVP_CIPHER_CTX *ctx;
ctx = EVP_CIPHER_CTX_new();

EVP_CipherInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, nullptr, 1);
EVP_CipherUpdate(ctx, encData, &outlen, (const unsigned char*)&g_data[0], inLen);
encLen = outlen;
EVP_CipherFinal(ctx, encData + outlen, &outlen);
encLen += outlen;
EVP_CIPHER_CTX_free(ctx);

std::cout << base64Encode(byte_array{ encData, encData + encLen }) << std::endl;

//解密
int decLen = 0;
outlen = 0;
unsigned char decData[1024];
EVP_CIPHER_CTX *ctx2;
ctx2 = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx2, EVP_aes_128_ecb(), NULL, key, nullptr, 0);
EVP_CipherUpdate(ctx2, decData, &outlen, encData, encLen);
decLen = outlen;
EVP_CipherFinal(ctx2, decData + outlen, &outlen);
decLen += outlen;
EVP_CIPHER_CTX_free(ctx2);

std::cout << std::string{ decData, decData + decLen } << std::endl;

return 0;
}

其它

  • 其它语言的有空慢慢加进来
  • C++编译用的CMake文件
    1
    2
    3
    4
    5
    6
    cmake_minimum_required (VERSION 3.8)
    find_package(OpenSSL REQUIRED)
    add_executable (encryptcpp
    "encryptcpp.cpp"
    "encryptcpp.h")
    target_link_libraries(encryptcpp PRIVATE OpenSSL::SSL OpenSSL::Crypto)

在Linux下只要装了CMake和OpenSSL就可以直接用了。Windows下各种麻烦,微软的工程师们估计也觉得有点坑,所以搞了一个vcpkg项目,可以和Linux的包管理一样安装代码库。VS可以直接引用,如果是CMake的话,要这样写:

1
cmake .."-DCMAKE_TOOLCHAIN_FILE=C:\vcpkg\scripts\buildsystems\vcpkg.cmake" -G"Visual Studio 15"

或者如果是用VS2017来管理CMake工程的话,菜单CMake\管理CMake设置,打开”CMakeSettings.json”文件,添加”variables”参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"configurations": [
{
"name": "x86-Debug",
"generator": "Visual Studio 15 2017",
"configurationType": "Debug",
"inheritEnvironments": [
"msvc_x86"
],
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "-m -v:minimal",
"ctestCommandArgs": "",
"variables": [
{
"name": "CMAKE_TOOLCHAIN_FILE",
"value": "C:\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake"
}
]
}
]
}