[原文地址:http://yidinghe.cnblogs.com/articles/449212.html]

今天碰上一件令我头大的事情。我们的系统要和一个外部系统进行通讯,传输方式是采用 DES 算法对消息进行加密,再用 BASE64 编码。不过对方系统是用 C# 写的。平台不一样,于是我和对面的老兄先测试一下加密解密。这一测试问题就来了。两边采用同样的密钥,对同一个字符串加密出来的结果不一样。怎么办呢?我先把他那边的代码要过来了,他是这样写的(C#):

    public static string Encrypt(string message, string key)
    {
        DES des 
= new DESCryptoServiceProvider();
        des.Key 
= Encoding.UTF8.GetBytes(key);
        des.IV 
= Encoding.UTF8.GetBytes(key);

        
byte[] bytes = Encoding.UTF8.GetBytes(message);
        
byte[] resultBytes = des.CreateEncryptor().TransformFinalBlock(bytes, 0, bytes.Length);

        
return Convert.ToBase64String(resultBytes);
    }  

而我这边呢,是用 Java 这样写的(仅仅加密到字节串,BASE64 编码是在另一个类里):

    public static byte[] desEncrypt(String message, String keythrows Exception {
        Cipher cipher 
= Cipher.getInstance("DES");

        DESKeySpec desKeySpec 
= new DESKeySpec(key.getBytes("UTF-8"));
        SecretKeyFactory keyFactory 
= SecretKeyFactory.getInstance("DES");
        SecretKey secretKey 
= keyFactory.generateSecret(desKeySpec);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        
return cipher.doFinal(message.getBytes("UTF-8"));
    }

两边一对比我就奇怪了,他那边怎么有个 IV (
initialization vector)?我打开JDK文档一看Cipher类,竟然也有一个getIV()方法。看来这 IV 大有来头。于是在JDK文档里面到处找IV,找到了一个叫“IvParameterSpec“的类,莫非就是它?它实现的是 AlgorithmParameterSpec 接口,而Cipher.init()中就有一个 AlgorithmParameterSpec 类型的参数。那么看来就是它了。于是参考了IvParameterSpec的构造函数后,我这样把它加进去:

    public static byte[] desEncrypt(String message, String key) throws Exception {
        Cipher cipher 
= Cipher.getInstance("DES");

        DESKeySpec desKeySpec 
= new DESKeySpec(key.getBytes("UTF-8"));

        SecretKeyFactory keyFactory 
= SecretKeyFactory.getInstance("DES");
        SecretKey secretKey 
= keyFactory.generateSecret(desKeySpec);
        IvParameterSpec iv 
= new IvParameterSpec(key.getBytes("UTF-8"));
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);

        
return cipher.doFinal(message.getBytes("UTF-8"));
    }

这下行了吧,结果一运行报异常:

java.security.InvalidAlgorithmParameterException: ECB mode cannot use IV

这又是什么?    “ECB”?  还是找吧,在JDK文档中找到了这么几句鬼东西:

the SunJCE provider uses ECB as the default mode, and PKCS5Padding as the default padding scheme for DES, DES-EDE and Blowfish ciphers. This means that in the case of the SunJCE provider,
    Cipher c1 = Cipher.getInstance("DES/
ECB/PKCS5Padding");
and
    Cipher c1 = Cipher.getInstance("DES");
are equivalent statements.

哦,问题就出在方法里的第一行。那么不准用 ECB,该用什么呢?在该文往下找,找到了这么几项:

The following names can be specified as the mode component in a transformation when requesting an instance of Cipher:

  • NONE: No mode.
  • CBC: Cipher Block Chaining Mode, as defined in FIPS PUB 81.
  • CFB: Cipher Feedback Mode, as defined in FIPS PUB 81.
  • ECB: Electronic Codebook Mode, as defined in: The National Institute of Standards and Technology (NIST) Federal Information Processing Standard (FIPS) PUB 81, "DES Modes of Operation," U.S. Department of Commerce, Dec 1980.
  • OFB: Output Feedback Mode, as defined in FIPS PUB 81.
  • PCBC: Propagating Cipher Block Chaining, as defined by Kerberos V4.

我这样都换着试一下就行了。哎,运气不错,拿着第一个 CBC 试一下就成功了,加密后的字符串也“正确”了。
我那个方法就变成这样:

    public static byte[] desEncrypt(String message, String key) throws Exception {
        Cipher cipher 
= Cipher.getInstance("DES/CBC/PKCS5Padding");

        DESKeySpec desKeySpec 
= new DESKeySpec(key.getBytes("UTF-8"));

        SecretKeyFactory keyFactory 
= SecretKeyFactory.getInstance("DES");
        SecretKey secretKey 
= keyFactory.generateSecret(desKeySpec);
        IvParameterSpec iv 
= new IvParameterSpec(key.getBytes("UTF-8"));
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);

        
return cipher.doFinal(message.getBytes("UTF-8"));
    }

这样,解决了这个 ECB 之后,Java 和 .net 平台的两个应用终于能够相互加密和解密数据了。什么?文不对题?加密都写出来了,解密还不简单?自己看着办吧。

[原文地址:http://yidinghe.cnblogs.com/articles/449212.html]