java與C#、.NET AES加密、解密 解決方案


1.情景展示

  Java提供的密鑰,C#無法解密。 

2.原因分析

  在Java中,AES的實際密鑰需要用到KeyGenerator 和 SecureRandom,但是C#和.NET 里面沒有這2個類,

  所以,無法使用安全隨機數生成KEY,進而導致解密失敗。

  Java對密鑰做的進一步處理:

  參數說明:

  加密模式:ECB(默認值)、CBC
  填充模式:PKCS5Padding(java只有這一種,其它語言使用PKCS7Padding即可,5和7沒有區別)
  數據塊:128位(java只有這一種)

3.解決方案

  超級簡單的方法見最后(20190921)

  方案一:推薦使用

  思路:

  將由Java生成的AES所需要的實際密鑰,提供給C#,然后C#用這個實際的key去解密。  

  由於C#中byte范圍是[0,255],而Java中的byte范圍是[-128,127],所以,我們需要對生成的二進制密鑰進行處理。

  因此,Java作為密鑰的提供方,需要將二進制轉成16進制,C#將接收到的16進制密鑰轉換成二進制即可。

  流程圖:

  java AES 加密

import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.log4j.Logger;
 
/**
 * AES加密算法工具類
 * @explain 可逆算法:加密、解密
 * AES/ECB/PKCS5Padding
 * @author Marydon
 * @creationTime 2018年7月7日下午2:17:43
 * @version 3.0
 * @since 2.0
 * @email marydon20170307@163.com
 */
public class AESUtils {
 
    private static Logger log = Logger.getLogger(AESUtils.class);
    // 定義字符集
    private static final String ENCODING = "UTF-8";
 
    /**
     * 根據提供的密鑰生成AES專用密鑰
     * @explain
     * @param password
     *            可以是中文、英文、16進制字符串
     * @return AES密鑰
     * @throws Exception
     */
    public static byte[] generateKey(String password) throws Exception {
        byte[] keyByteArray = null;
        // 創建AES的Key生產者
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        // 利用用戶密碼作為隨機數初始化
        // 指定強隨機數的生成方式
        // 兼容linux
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(password.getBytes(ENCODING));
        kgen.init(128, random);// 只能是128位
         
        // 根據用戶密碼,生成一個密鑰
        SecretKey secretKey = kgen.generateKey();
        // 返回基本編碼格式的密鑰,如果此密鑰不支持編碼,則返回null。
        keyByteArray = secretKey.getEncoded();
        return keyByteArray;
    }
    
	/**
	 * AES加密字符串
	 * @param content
	 *            需要被加密的字符串
	 * @param password
	 *            加密需要的密碼
	 * @return 16進制的密文(密文的長度隨着待加密字符串的長度變化而變化,至少32位)
	 */
	public static String encrypt(String content, String password) {
	    String cipherHexString = "";// 返回字符串
	    try {
	        // 轉換為AES專用密鑰
	    	byte[] keyBytes = generateKey(password);
	    	
	    	SecretKeySpec sks = new SecretKeySpec(keyBytes, "AES");
	    	// 將待加密字符串轉二進制
	        byte[] clearTextBytes = content.getBytes(ENCODING);
	        // 創建密碼器,默認參數:AES/EBC/PKCS5Padding
	        Cipher cipher = Cipher.getInstance("AES");
	        // 初始化為加密模式的密碼器
	        cipher.init(Cipher.ENCRYPT_MODE, sks);
	        // 加密結果
	        byte[] cipherTextBytes = cipher.doFinal(clearTextBytes);
	        // byte[]-->hexString
	        cipherHexString = ByteUtils.toHex(cipherTextBytes);
	    } catch (Exception e) {
	        e.printStackTrace();
	        log.error("AES加密失敗:" + e.getMessage());
	    }
	    log.info("AES加密結果:" + cipherHexString);
	    return cipherHexString;
	}
}

  先調用generateKey()方法,然后將二進制轉換成16進制(如果byte[]轉16進制不會,見文末鏈接)。

  C# AES 解密

/// <summary>
///  AES 解密
/// </summary>
/// <param name="toDecrypt">密文(待解密)</param>
/// <param name="hexKey">密鑰(16進制)</param>
/// <returns></returns>
public static string AesDecrypt(string toDecrypt, string hexKey)
{
    if (string.IsNullOrEmpty(toDecrypt)) return null;
    //將16進制的密文轉為字節數組
    var toDecryptArray = new byte[toDecrypt.Length / 2];
    for (var x = 0; x < toDecryptArray.Length; x++)
    {
        var i = Convert.ToInt32(toDecrypt.Substring(x * 2, 2), 16);
        toDecryptArray[x] = (byte)i;
    }

    //將16進制的秘鑰轉成字節數組
    var keyArray = new byte[hexKey.Length / 2];
    for (var x = 0; x < keyArray.Length; x++)
    {
        var i = Convert.ToInt32(hexKey.Substring(x * 2, 2), 16);
        keyArray[x] = (byte)i;
    }

    RijndaelManaged rm = new RijndaelManaged
    {
        Key = keyArray,
        Mode = CipherMode.ECB,//必須設置為ECB
        Padding = PaddingMode.PKCS7//必須設置為PKCS7
    };

    ICryptoTransform cTransform = rm.CreateDecryptor();
    Byte[] resultArray = cTransform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length);

    return Encoding.UTF8.GetString(resultArray);
} 

  測試

public static void main(String[] args) throws Exception {
    String text = "Marydon";
    String password = "521";
    System.out.println(ByteUtils.toHex(generateKey(password)));// FB511ED54B1B3D71093309D4F6DEBD61
    // 加密
    String encrypt = encrypt(text, password);// 7468F296C547B321AE1086741BAC13C4
}

  方案二:密鑰使用16位的,自行百度。

  方案三: 改變填充模式

  Java默認的填充模式為PKCS5Padding,可以將Java和C#統一采用NoPadding,需要自己定義這種填充模式。

  方案四:使用dll動態庫實現。

2019/05/08

.NET的解決方案與C#一樣。

/// <summary> 
/// 將16進制字符串轉二進制
/// </summary> 
/// <param name="hexString">需要進行解碼的字符串</param> 
/// <returns></returns> 
public static byte[] HexStrToByte(string hexString)
{
    hexString = hexString.Replace(" ", "");
    if ((hexString.Length % 2) != 0)
        hexString += " ";
    byte[] returnBytes = new byte[hexString.Length / 2];
    for (int i = 0; i < returnBytes.Length; i++)
        returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
    return returnBytes;
}

/// <summary>
///  AES 解密
/// </summary>
/// <param name="str">密文(待解密)</param>
/// <param name="key">密鑰</param>
/// <returns></returns>
public static string AesDecrypt(string str, string key)
{
    if (string.IsNullOrEmpty(str)) return null;
    //將16進制密文轉為字節數組
    var toEncryptArray = new byte[str.Length / 2];
    for (var x = 0; x < toEncryptArray.Length; x++)
    {
        var i = Convert.ToInt32(str.Substring(x * 2, 2), 16);
        toEncryptArray[x] = (byte)i;
    }

    //將16進制秘鑰轉成字節數組
    var inputByteArray = HexStrToByte(key);

    RijndaelManaged rm = new RijndaelManaged
    {
        Key = inputByteArray,
        Mode = CipherMode.ECB,//必須設置為ECB
        Padding = PaddingMode.PKCS7//必須設置為PKCS7
    };

    ICryptoTransform cTransform = rm.CreateDecryptor();
    Byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

    return Encoding.UTF8.GetString(resultArray);
}

2019/08/28

C# AES加密

/// <summary>
///  AES 加密
/// </summary>
/// <param name="toEncrypt">明文(待加密)</param>
/// <param name="hexKey">密鑰(確保java提供給你的是16進制密鑰,不是十進制!)</param>
/// <returns>AES加密結果</returns>
public static string AesEncrypt(string toEncrypt, string hexKey)
{
    //將16進制秘鑰轉成字節數組
    var keyArray = new byte[hexKey.Length / 2];
    for (var x = 0; x < keyArray.Length; x++)
    {
        var i = Convert.ToInt32(hexKey.Substring(x * 2, 2), 16);
        keyArray[x] = (byte)i;
    }
    
    byte[] toEncryptArray = Encoding.UTF8.GetBytes(toEncrypt);
    RijndaelManaged rDel = new RijndaelManaged();
    rDel.Key = keyArray;
    rDel.Mode = CipherMode.ECB;
    rDel.Padding = PaddingMode.PKCS7;

    ICryptoTransform cTransform = rDel.CreateEncryptor();
    byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

    return ByteArrayToHexString(resultArray);
}

/// <summary>
/// 將一個byte數組轉換成一個格式化的16進制字符串
/// </summary>
/// <param name="data">byte數組</param>
/// <returns>格式化的16進制字符串</returns>
public static string ByteArrayToHexString(byte[] data)
{
    StringBuilder sb = new StringBuilder(data.Length * 3);
    foreach (byte b in data)
    {
        sb.Append(Convert.ToString(b, 16).PadLeft(2, '0'));
    }
    return sb.ToString().ToUpper();
}

解決方案五:20190921 最省心

  在實際對接過程中,每次都要和對方解釋半天才能搞定,真是浪費時間,我們不妨這樣想一想:

  由於密碼生成器是java所獨有的,其它語言都不支持(IOS,ANDROID,C#,.NET等),既然java這么特立獨行,我們是不是可以不使用這個密碼生成器呢?

  經實踐發現,只要不用java特有的密鑰生成器對初始密鑰做進一步處理,不論是哪一種語言,其加密結果都是一致的。

/**
 * AES加密字符串(兼容任何語言)
 * @explain 沒有使用java獨有的密碼生成器
 * @param content
 *            需要被加密的字符串
 * @param password
 *            加密需要的密碼
 * @return 16進制的密文(密文的長度隨着待加密字符串的長度變化而變化,至少32位)
 */
public static String encrypt(String content, String password) {
    String cipherHexString = "";// 返回字符串
    try {
        // 轉換為AES專用密鑰(這一步直接廢棄)
        // byte[] keyBytes = generateKey(password);
        
        // 直接當做密鑰使用(除java以外的語言,其它語言都是直接把它當做密鑰)
        byte[] keyBytes = password.getBytes(ENCODING);
        SecretKeySpec sks = new SecretKeySpec(keyBytes, "AES");
        // 將待加密字符串轉byte[]
        byte[] clearTextBytes = content.getBytes(ENCODING);
        // 創建密碼器
        Cipher cipher = Cipher.getInstance("AES");
        // 初始化為加密模式的密碼器
        cipher.init(Cipher.ENCRYPT_MODE, sks);
        // 加密結果
        byte[] cipherTextBytes = cipher.doFinal(clearTextBytes);
        // byte[]-->hexString
        cipherHexString = ByteUtils.toHex(cipherTextBytes);
    } catch (Exception e) {
        e.printStackTrace();
        log.error("AES加密失敗:" + e.getMessage());
    }
    log.info("AES加密結果:" + cipherHexString);
    return cipherHexString;
}

2019/10/14

php加密,解密

/**
 * 加密
 *
 * @param $str
 * @return string
 */
function encrypt($str){
    $secret = $this->getSecret();
    $data = openssl_encrypt($str, 'aes-128-ecb', $secret, OPENSSL_PKCS1_PADDING);
    $data = bin2hex($data)
    return $data;
}

/**
 * 解密
 *
 * @param $str
 */
function decrypt($str){
    $data = hex2bin($str);
    $secret = $this->getSecret();
    $data = openssl_decrypt($data, 'aes-128-ecb', $secret, OPENSSL_PKCS1_PADDING);
    return $data;
}

/**
 * 密鑰處理
 */
function getSecret(){
    // 16進制密鑰
    $hex = 'F07D896FD9098039D0F666525FD9EDE2';
    // 轉二進制
    $hex = pack('H*', $hex);
    return $hex;
}

2019/12/27

oracle加密

CREATE OR REPLACE FUNCTION FUN_ENCRYPT_AES(V_STR VARCHAR2,
                                           V_KEY VARCHAR2)
  RETURN VARCHAR2 AS
  V_KEY_RAW    RAW(32);
  V_STR_RAW    RAW(2000);
  V_RETURN_STR VARCHAR2(2000);
  V_TYPE       PLS_INTEGER;
BEGIN
  /*************************************************
  加密函數 FUN_ENCRYPT_AES
  入參:
  V_STR 待加密字符串
  V_KEY 密鑰
  返回值:
  V_RETURN_STR 返回密文字符串,約定返回為 16進制密文字符串
  
  加密方式 128/ebc/pkcs5
  密鑰位數:AES128 DBMS_CRYPTO.ENCRYPT_AES128
  連接方式:EBC DBMS_CRYPTO.CHAIN_EBC
  填充方式:PKCS5 DBMS_CRYPTO.PAD_PKCS5 
  **************************************************/
  --將字符串varchar2轉換成位串raw,並按照utf-8格式進行解析  
  V_KEY_RAW := UTL_I18N.STRING_TO_RAW(V_KEY, 'AL32UTF8');
  /*V_KEY_RAW := '40146E5CA7AF57D01959C0FAFB7B7330';*/
  V_STR_RAW := UTL_I18N.STRING_TO_RAW(V_STR, 'AL32UTF8');
  /*注意:需保證當前登錄用戶有調用包DBMS_CRYPTO的權限*/
  -- 指定:密鑰算法、工作模式、填充方式
  V_TYPE       := DBMS_CRYPTO.ENCRYPT_AES128 + DBMS_CRYPTO.CHAIN_ECB +
                  DBMS_CRYPTO.PAD_PKCS5;
  V_STR_RAW    := DBMS_CRYPTO.ENCRYPT(SRC => V_STR_RAW,
                                      TYP => V_TYPE,
                                      KEY => V_KEY_RAW);
  V_RETURN_STR := RAWTOHEX(V_STR_RAW);
  RETURN V_RETURN_STR;

END;

  注意:這里的Key也是需要Java提前轉換成16進制的密鑰。

2020/01/02

powerbuilder(PB)

//AES/ECB/PKCS5Padding 加密 以 Hex 編碼返回
blob lbb_iv 
ls_param_encrypt = _codec.HexEncode(_crypto.SymEncrypt(1, _codec.ToUTF8(ls_param_set),  _codec.HexDecode(is_aes_key), _crypto.CIPHER_MODE_ECB, lbb_iv))

2020/01/08

javascript

前提:引入cryptojs文件

<script type="text/javascript" src="crypt/crypto-js.js"></script>

aes加密、解密

/**
 * aes加密
 * @param clearText 待加密字符串
 * @param hexKey 加密密鑰,由java提供(16進制)
 * @explain AES/ECB/PKCS7Padding
 * 偏移量(填充方式):PKCS7Padding,對應java的PKCS5Padding
 * 加密模式:ECB
 * return 加密結果(16進制)
 */
function encrypt(clearText, hexKey) {
    var key = CryptoJS.enc.Hex.parse(hexKey);
    var encrytedData = CryptoJS.AES.encrypt(clearText, key, {
        mode : CryptoJS.mode.ECB,
        padding : CryptoJS.pad.Pkcs7
    });
    return encrytedData.ciphertext.toString().toUpperCase();
}

/**
 * aes解密
 * @param cipherText 待解密字符串(16進制)
 * @param hexKey 解密密鑰,由java提供(16進制)
 * return 解密結果(以utf-8進行編碼)
 */
function decrypt(cipherText, hexKey) {
    // 1.將16進制轉換成數組
    var hexArray = CryptoJS.enc.Hex.parse(cipherText);
    // 2.將數組轉換成base64字符串
    var base64Str = CryptoJS.enc.Base64.stringify(hexArray);
    // 3.將密鑰轉換成數組
    var key = CryptoJS.enc.Hex.parse(hexKey);
    // 4.解密
    var decryptedData = CryptoJS.AES.decrypt(base64Str, key, {
        mode : CryptoJS.mode.ECB,
        padding : CryptoJS.pad.Pkcs7
    });
    // 5.以utf-8進行編碼解密結果
    return decryptedData.toString(CryptoJS.enc.Utf8);
}  

測試

window.onload = function() {
    var clearText = "張三";
    var hexKey = "E341BACB74574E03051D2BB1FD48BD99";
    var cipherText = encrypt(clearText, hexKey);
    console.log(cipherText);//6E3AC3434E1F8C371EA81DDB124AA5D7
    clearText = decrypt(cipherText,hexKey);
    console.log(clearText);//張三
    
}

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM