Java==>C# IKVM加密解密机AES对称加密模式AES-128-ECB,补码方式 PKCS5Padding
java与C#、.NET AES加密、解密 解决方案
Created by Marydon on 2018-10-24 16:07
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 加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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 解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /// <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); } |
测试
1 2 3 4 5 6 7 | 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#一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /// <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加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /// <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特有的密钥生成器对初始密钥做进一步处理,不论是哪一种语言,其加密结果都是一致的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /** * 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加密,解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /** * 加密 * * @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加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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)
1 2 3 | //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文件
1 | < script type="text/javascript" src="crypt/crypto-js.js"></ script > |
aes加密、解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /** * 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); } |
测试
1 2 3 4 5 6 7 8 9 | window.onload = function () { var clearText = "张三" ; var hexKey = "E341BACB74574E03051D2BB1FD48BD99" ; var cipherText = encrypt(clearText, hexKey); console.log(cipherText); //6E3AC3434E1F8C371EA81DDB124AA5D7 clearText = decrypt(cipherText,hexKey); console.log(clearText); //张三 } |
2021年12月13日15:00:20
如果AES解密失败报错信息如下:
java.security.InvalidKeyException: Illegal key size or default parameters
解决办法:升级jdk版本
比方说:在本地加解密是没有问题的,但是,部署到服务器上却解密失败,原因在于:
最简单的办法就是:将jdk版本升级到1.8.0_251版本以上。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?