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

}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章