Max.Bai
2019-02
最近和java项目对接遇到AES加密算法,java代码有SecureRandom.getInstance("SHA1PRNG"); python实在找不到对应的方法,C#,php,js代码各种查到,大家都有遇到,解决的不多,C# 直接用java算出key,然后用C#再算AES(https://blog.csdn.net/yunhua_lee/article/details/17226089),耗时差不多2天,最终在php代码中找到方法(https://github.com/myGGT/crypt_aes/blob/master/crypt_aes.php),相关JavaScript代码(https://github.com/bombworm/SHA1PRNG/blob/master/index.js),记录下来给大家使用。
Java 加密参数说明(使用库)
AES加密模式:ECB/CBC/CTR/OFB/CFB
填充:pkcs5padding/pkcs7padding/zeropadding/iso10126/ansix923
数据块:128位/192位/256位
我们就以java默认AES加密方法为例,其他加密模拟基本都是对key的处理一样。Java默认AES加密模式是"AES/ECB/PKCS5Padding"。
java代码:
public static String AES_Encode(String encodeRules,String content){
try {
//1.构造密钥生成器,指定为AES算法,不区分大小写
KeyGenerator keygen=KeyGenerator.getInstance("AES");
//2.根据ecnodeRules规则初始化密钥生成器
//生成一个128位的随机源,根据传入的字节数组
//keygen.init(128, new SecureRandom(encodeRules.getBytes()));
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(encodeRules.getBytes());
keygen.init(128, secureRandom);
//3.产生原始对称密钥
SecretKey original_key=keygen.generateKey();
//4.获得原始对称密钥的字节数组
byte [] raw=original_key.getEncoded();
//5.根据字节数组生成AES密钥
SecretKey key=new SecretKeySpec(raw, "AES");
//6.根据指定算法AES自成密码器
Cipher cipher=Cipher.getInstance("AES");
//7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
cipher.init(Cipher.ENCRYPT_MODE, key);
//8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
byte [] byte_encode=content.getBytes("utf-8");
//9.根据密码器的初始化方式--加密:将数据加密
byte [] byte_AES=cipher.doFinal(byte_encode);
//10.将加密后的数据转换为字符串
//这里用Base64Encoder中会找不到包
//解决办法:
//在项目的Build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。
//String AES_encode=new String(new BASE64Encoder().encode(byte_AES));
String AES_encode = new String(bytesToHexString(byte_AES));
//11.将字符串返回
return AES_encode;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//如果有错就返加nulll
return null;
}
最主要的代码:
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(keyWord.getBytes());
kgen.init(128, secureRandom);
SecretKey secretKey = kgen.generateKey();
这几行就是要把密码进行了别的加密。坑就在这里,stackoverflow上建议修改java代码,1.指明加密方式和填充 2.不要使用SecureRandom,这个是Oracle实现的,可能在不同版本会产生不同的值,特别是Android(https://blog.csdn.net/banking17173/article/details/8236028)。 stackoverflow(https://stackoverflow.com/questions/24124091/better-way-to-create-aes-keys-than-seeding-securerandom)。
安装AES相关库
pip3 install pycryptodome
实现代码:
from base64 import b64encode, encodebytes
from Crypto.Cipher import AES
import binascii
import hashlib
BS = AES.block_size
def padding_pkcs5(value):
return str.encode(value + (BS - len(value) % BS) * chr(BS - len(value) % BS))
def padding_zero(value):
while len(value) % 16 != 0:
value += '\0'
return str.encode(value)
def aes_ecb_encrypt(key, value):
# AES/ECB/PKCS5padding
# key is sha1prng encrypted before
cryptor = AES.new(bytes.fromhex(key), AES.MODE_ECB)
padding_value = padding_pkcs5(value) # padding content with pkcs5
ciphertext = cryptor.encrypt(padding_value)
return ''.join(['%02x' % i for i in ciphertext]).upper()
def get_sha1prng_key(key):
'''[summary]
encrypt key with SHA1PRNG
same as java AES crypto key generator SHA1PRNG
Arguments:
key {[string]} -- [key]
Returns:
[string] -- [hexstring]
'''
signature = hashlib.sha1(key.encode()).digest()
signature = hashlib.sha1(signature).digest()
return ''.join(['%02x' % i for i in signature]).upper()[:32]
hexstr_content = '405EE11002F3' #content
key = '12532802' #keypassword
expect_result = 'c1ee1f3f2d74e02706be9af78aa79ba4'.upper()
aes128string = aes_ecb_encrypt(get_sha1png_key(key), hexstr_content)
print(aes128string)
关键代码:
def get_sha1prng_key(key):
'''[summary]
encrypt key with SHA1PRNG
same as java AES crypto key generator SHA1PRNG
Arguments:
key {[string]} -- [key]
Returns:
[string] -- [hexstring]
'''
signature = hashlib.sha1(key.encode()).digest()
signature = hashlib.sha1(signature).digest()
return ''.join(['%02x' % i for i in signature]).upper()[:32]
实现key的转换,实现了java中关键代码的内容。这里返回的是16进制字符串,你可以修改代码返回想要的格式。
实现了关键代码,其他CBC等加密方式都一样。
AES主要注意两个:
1. 加密方法ECB,CBC等。
2. 对key的处理,比如java的sha1prng,或者base64等。
3. 填充,key和原始文本都有可能填充,NoPadding,不填充,0填充,还有pkcs5padding, 不填充就是不对内容填充,直接加密,上面代码实现了\0填充和pkcs5padding 。
总的来说加密内容对不上基本是key处理不一样或者填充不对。
0x04 完整代码
完整代码包含了一点解密代码,不过解密代码没有填充,如果有填充的话,解密完内容需要去掉填充。
from base64 import b64encode, encodebytes
from Crypto.Cipher import AES
import binascii
import hashlib
BS = AES.block_size
def padding_pkcs5(value):
return str.encode(value + (BS - len(value) % BS) * chr(BS - len(value) % BS))
def padding_zero(value):
while len(value) % 16 != 0:
value += '\0'
return str.encode(value)
def aes_ecb_encrypt(key, value):
''' AES/ECB/NoPadding encrypt '''
key = bytes.fromhex(key)
cryptor = AES.new(key, AES.MODE_ECB)
ciphertext = cryptor.encrypt(bytes.fromhex(value))
return ''.join(['%02x' % i for i in ciphertext]).upper()
def aes_ecb_decrypt(key:str, value:str) -> str:
''' AES/ECB/NoPadding decrypt '''
key = bytes.fromhex(key)
cryptor = AES.new(key, AES.MODE_ECB)
ciphertext = cryptor.decrypt(bytes.fromhex(value))
return ''.join(['%02x' % i for i in ciphertext]).upper()
def get_userkey(key, value):
''' AES/ECB/PKCS5Padding encrypt '''
cryptor = AES.new(bytes.fromhex(key), AES.MODE_ECB)
padding_value = padding_pkcs5(value)
ciphertext = cryptor.encrypt(padding_value)
return ''.join(['%02x' % i for i in ciphertext]).upper()
def get_sha1prng_key(key):
'''[summary]
encrypt key with SHA1PRNG
same as java AES crypto key generator SHA1PRNG
Arguments:
key {[string]} -- [key]
Returns:
[string] -- [hexstring]
'''
signature = hashlib.sha1(key.encode()).digest()
signature = hashlib.sha1(signature).digest()
return ''.join(['%02x' % i for i in signature]).upper()[:32]
data = '0513011E0005016400000000000000000001000000000000000000000000000000770013011E0005026400000000000000000001000000000000000000000000000000770013011E0005036400000000000000000001000000000000000000000000000000770013011E0005046400000000000000000001000000000000000000000000000000770013011E000505640000000000000000000100000000000000000000000000000077000000000000'
key = '43C8B53E236C4756B8FF24E5AA08A549'
aes_result = 'AB4F4686218A5FF9F07E5248E6B5525D140602A0FAA21176C9A158A010B1A7C0258E80667BF7DD3B6FF57707B373BF75F57AE634D9F1384002AA6B788F4C658DD77572C207AAE3134F91FB690A4F024EF428DE3E1C5F84D0EA9D01B8AB4ED9FE97D7C0D65D447D92F0E306573F30E1360B3DE999E952BAAB9B22E48B8C7B23DC5480027DEE44988F0E86F7A475EEF599C1D7D3331457E582558BC3447E644913ABD63FC221C2E0D49BD712879261FF5F'
aes128string = aes_ecb_encrypt(key, data)
print(aes128string)
aes128string = aes_ecb_decrypt(key, aes_result)
print(aes128string)
mac = '405EE11002F3'
device_id = '12532802' #'12403492' #'12532802'
user_key = 'c1ee1f3f2d74e02706be9af78aa79ba4'.upper()
aes128string = get_userkey(get_sha1png_key(device_id), mac)
print(aes128string)
# 58F7CD929BFAA0915032536FBA8D3281420E93E3
# 58f7cd929bfaa0915032536fba8d3281
# 58F7CD929BFAA0915032536FBA8D3281
print(get_sha1png_key('12532802')) # 58f7cd929bfaa0915032536fba8d3281
完整java代码:
package javatest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.File;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Scanner;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
//import sun.misc.BASE64Decoder;
//import sun.misc.BASE64Encoder;
//import RC4;
public class AESEncode {
public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
private static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
public static String AES_Encode(String encodeRules,String content){
try {
//1.构造密钥生成器,指定为AES算法,不区分大小写
KeyGenerator keygen=KeyGenerator.getInstance("AES");
//2.根据ecnodeRules规则初始化密钥生成器
//生成一个128位的随机源,根据传入的字节数组
//keygen.init(128, new SecureRandom(encodeRules.getBytes()));
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(encodeRules.getBytes());
kgen.init(128, secureRandom);
//3.产生原始对称密钥
SecretKey original_key=keygen.generateKey();
//4.获得原始对称密钥的字节数组
byte [] raw=original_key.getEncoded();
//5.根据字节数组生成AES密钥
SecretKey key=new SecretKeySpec(raw, "AES");
//6.根据指定算法AES自成密码器
Cipher cipher=Cipher.getInstance("AES");
//7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
cipher.init(Cipher.ENCRYPT_MODE, key);
//8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
byte [] byte_encode=content.getBytes("utf-8");
//9.根据密码器的初始化方式--加密:将数据加密
byte [] byte_AES=cipher.doFinal(byte_encode);
//10.将加密后的数据转换为字符串
//这里用Base64Encoder中会找不到包
//解决办法:
//在项目的Build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。
//String AES_encode=new String(new BASE64Encoder().encode(byte_AES));
String AES_encode = new String(bytesToHexString(byte_AES));
//11.将字符串返回
return AES_encode;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//如果有错就返加nulll
return null;
}
/*
* 解密
* 解密过程:
* 1.同加密1-4步
* 2.将加密后的字符串反纺成byte[]数组
* 3.将加密内容解密
*/
public static String AES_Decode(String encodeRules,String content){
String ss;
try {
//1.构造密钥生成器,指定为AES算法,不区分大小写
KeyGenerator keygen=KeyGenerator.getInstance("AES");
//2.根据ecnodeRules规则初始化密钥生成器
//生成一个128位的随机源,根据传入的字节数组
//keygen.init(128, new SecureRandom(encodeRules.getBytes()));
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(encodeRules.getBytes());
kgen.init(128, secureRandom);
//3.产生原始对称密钥
SecretKey original_key=keygen.generateKey();
//4.获得原始对称密钥的字节数组
byte [] raw=original_key.getEncoded();
//5.根据字节数组生成AES密钥
SecretKey key=new SecretKeySpec(raw, "AES");
//6.根据指定算法AES自成密码器
Cipher cipher=Cipher.getInstance("AES");
//7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY
cipher.init(Cipher.DECRYPT_MODE, key);
//8.将加密并编码后的内容解码成字节数组
byte [] byte_content=hexStringToBytes(content);
//byte [] byte_content= new BASE64Decoder().decodeBuffer(content);
//System.out.println("ttttttttttttttttttttttttttt");
//System.out.println(new String(byte_content, "UTF-8"));
/*
* 解密
*/
byte [] byte_decode=cipher.doFinal(byte_content);
String AES_decode=new String(byte_decode,"utf-8");
return AES_decode;
} catch (NoSuchAlgorithmException e) {
ss = "NoSuchAlgorithmException";
e.printStackTrace();
} catch (NoSuchPaddingException e) {
ss = "NoSuchPaddingException";
e.printStackTrace();
} catch (InvalidKeyException e) {
ss = "InvalidKeyException";
e.printStackTrace();
} catch (IOException e) {
ss = "IOException";
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
ss = "IllegalBlockSizeException";
e.printStackTrace();
} catch (BadPaddingException e) {
ss = "BadPaddingException";
e.printStackTrace();
}
//如果有错就返加nulll
return ss;
}
public static void main(String[] args) {
//MyClass se=new MyClass();
//Scanner scanner=new Scanner(System.in);
/*
* 加密
*/
System.out.println("使用AES对称加密,请输入加密的规则");
//String encodeRules=scanner.next();
System.out.println("请输入要加密的内容:");
String encodeRules = "a02254eb247146dfa446c4b2795ebf90";
String content = "32770";
String outstr = AES_Encode(encodeRules, content);
System.out.println("根据输入的规则"+encodeRules+"加密后的密文是:"+outstr);
/*
* 解密
*/
System.out.println("使用AES对称解密,请输入加密的规则:(须与加密相同)");
String byte_str = "0212f41b372fe457c3f7df0757151615";
outstr = AES_Decode(encodeRules, byte_str);
System.out.println("根据输入的规则"+encodeRules+"解密后的明文是:"+outstr);
}
}