python3 - AES 加密实现java中SHA1PRNG 算法

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);
    
}

}

 

 

 

你可能感兴趣的:(Python)