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

1.情景展示

  Java提供的密钥,C#无法解密。 

2.原因分析

  在Java中,AES的实际密钥需要用到KeyGenerator 和 SecureRandom,但是C#和.NET 里面没有这2个类,

  所以,无法使用安全随机数生成KEY,进而导致解密失败。

  Java对密钥做的进一步处理:

java与C#、.NET AES加密、解密 解决方案_第1张图片

  参数说明:

  加密模式: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与C#、.NET AES加密、解密 解决方案_第2张图片

  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 [email protected]
 */
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 解密

/// 
///  AES 解密
/// 
/// 密文(待解密)
/// 密钥(16进制)
/// 
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#一样。

///  
/// 将16进制字符串转二进制
///  
/// 需要进行解码的字符串 
///  
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;
}

/// 
///  AES 解密
/// 
/// 密文(待解密)
/// 密钥
/// 
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加密

/// 
///  AES 加密
/// 
/// 明文(待加密)
/// 密钥(确保java提供给你的是16进制密钥,不是十进制!)
/// AES加密结果
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);
}

/// 
/// 将一个byte数组转换成一个格式化的16进制字符串
/// 
/// byte数组
/// 格式化的16进制字符串
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;
}

  

 相关推荐:

  • java AES加密、解密(兼容windows和linux)
  • java byte数组与16进制间的相互转换
  • aes在线测试网站

 

你可能感兴趣的:(java与C#、.NET AES加密、解密 解决方案)