Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 584|回复: 0

java与C#、.NET AES加密、解密 解决方案

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-4-24 15:03:15 | 显示全部楼层 |阅读模式

    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 = 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);//张三
        
    }
    

       

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-23 17:57 , Processed in 0.064985 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表