python3 - Java AES 加密實現java中SHA1PRNG 算法
Max.Bai
2019-02
0x00 事由
最近和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),記錄下來給大家使用。
0x01 Java實現
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)。
0x02 Python3實現
安裝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等加密方式都一樣。
0x03 總結
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);
}
}