1.情景展示

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

2.原因分析

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

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

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

3.解决方案

  方案一:推荐使用

  思路:

  将由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);
	        // 创建密码器
	        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进制。

  C# AES 解密

/// <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 = new byte[key.Length / 2];
    for (var x = 0; x < inputByteArray.Length; x++)
    {
        var i = Convert.ToInt32(key.Substring(x * 2, 2), 16);
        inputByteArray[x] = (byte)i;
    }

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

  测试

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

  

 

转载于:https://www.cnblogs.com/Marydon20170307/p/9844052.html

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐