首页 > 代码库 > Java密码学原型算法实现——第二部分:单钥加密算法

Java密码学原型算法实现——第二部分:单钥加密算法

题注

本部分为单钥加密算法的实现。单钥加密体制是密码学加密中的核心密码学原型之一,很早很早前人类就已经开始了单钥密码学体制的研究。本部分的所有实现基于Bouncy Castle库,其地址详见我上一篇博客《Java密码学原型算法实现——第一部分:标准Hash算法》。

不得不说,相比Java JDK的API,Bouncy Castle提供的库函数封装更为科学,实现更为方便。实际上,Bouncy Castle根据单钥体制的不同算法以及不同工作模式进行了不同的封装,只要实现了一种模式或者一种加密算法,就能够简单地修改源代码而实现不同算法之间的转换。因此,我们不得不佩服Bouncy Castle的实现者们!在此,我强烈建议有兴趣的研究者阅读一下Bouncy Castle的源代码,一定会受益匪浅。当然了,在阅读之前需要熟悉Java的一些基本封装机制,我个人认为先学习下Java JDK源代码中的Inputstream及其相关类会对理解Bouncy Castle有很大的帮助。

本章理论说明的插图来自HappyHippy的博客,其博客地址为http://www.cnblogs.com/happyhippy/archive/2006/12/23/601353.html。我个人其实找了很多已经画好的加密流程图,感觉HappyHippy的流程图是最清楚,最详尽的,在此表示感谢。同时,为尊重作者的网络资源,特给出引用地址。

作者:Silent Void
出处:http://happyhippy.cnblogs.com/

另外,需要吐槽几点:

(1)网上很多有关Bouncy Castle单钥体制的实现并不充分。实际上,单钥加密体制远非加密算法的调用那么简单,其实还涉及到分组补位、格式化输出、加密工作模式选择等等问题。本文尽力解决上面提到的所有问题,给大家提供一个比较好的通用使用方法。

(2)本文只实现了单钥体制中最为流行的两个算法:流加密中的RC4算法以及分组加密中的AES算法。DES算法的密钥长度是56位,现在已不能认为其是安全的加密算法。Triple-DES同理,而且Triple-DES虽然密钥长度是DES的三倍,但是其安全性只是DES的二倍,现在也不能认为是一个安全的单钥加密算法。甚至,现在已经不推荐使用128bit密钥长度的AES加密算法实现加密。Bouncy Castle中完全实现了现有的全部单钥加密体制,但是我这里指给出AES加密算法的实现。如果大家需要用其他的算法,只需要做很小的改动即可实现,不过具体的改动位置还请大家自行研究。我可以明确告知,只需要改1-2行代码即可。

单钥密码体制

单钥密码体制简介

单钥加密体制也成为私钥加密体制(Secret Key Cryptosystem),这实际上是与1976年以后出现的公钥加密体制(Public Key Cryptosystem)加以区分。由于通信双方采用的密钥相同,所以人们也通常称其为对称加密体制(Symmetric Cryptosystem)。一般来说,单钥加密体制和双钥加密体制是一对词语,私钥加密体制和公钥加密体制是一对词语,而对称加密体制和非对称加密体制是一对词语,大家进行介绍或者撰写说明文档时候需要特别注意一下词语的对应问题。

对于单钥加密体制来说,可以按照其加密运算的特点,将其分为流密码(Stream Cipher)和分组密码(Block Cipher)。根据其工作模式的分类,又可以分为5类:电码本模式(ECB Mode),密码分组链接模式(CBC Mode),输出反馈模式(OFB Mode),密码反馈模式(CFB Mode)和计数器模式(CTR Mode)。由于安全性的考虑,一般我们只使用ECB,CBC或者OFB模式。不过,对于学习单钥密码体制,尤其是深入加密盒内部的工作原理,一般以ECB模式进行介绍。

1883年,Kerchoffs列举了一个设计密码要求必备的条件表。结合香农对密码体制的语义描述和Kerchoffs原理,可以对好的密码体制做如下总结:

  • 加密算法和解密算法不包含密码的成分或设计部分。这句话说白了就是密码学算法应该是完全公开的,密文是否安全只取决于密钥的安全性,而与算法的公开与否无关。当然了,不公开的加密算法从实际操作中会增加密码的安全性。举例来说,在移动通信网络中的A5和A8算法就是不公开的加密算法。不过这也情有可原:本来手机刚出来时能存储的密钥长度就不长,加密算法又不能太复杂,所以设计的加密算法相对来说安全性也不会非常高… 据称,如果公开了A5和A8算法,那么大家的手机通信就危险啦~
  • 加密算法将有意义的消息相当均匀地分布在整个密文消息空间中;甚至可以由加密算法的某些随机的内部运算来获得随机的分布。这也就是说,加密算法的输出应该非常的随机,甚至可以作为一个随机数产生器。
  • 使用正确的密钥,加密算法和解密算法是实际有效的。这句话是说,如果拥有正确的私钥,那么解密算法可以正确地恢复出加密算法加密的明文。
  • 不使用正确的密钥,要由密文恢复出相应的明文是一个由密钥参数的大小唯一确定的困难问题。这句话即要求加密算法的安全性。

当然了,密码体制具有以上这些性质对于现代密码体制的应用来说已经不够了。现代密码学对于密码体制提出了更高的要求,如:选择明文安全性(Chosen-Plaintext Security),选择密文安全性(Chosen-Ciphertext Security)等等。有兴趣的朋友可以听一听Stanford大学Dan Boneh教授的公开课《Cryptography I》,会对单钥密码体制的安全性有更为深刻的理解。

流密码

流密码是密码体制中的一个重要体制,也是手工和机械密码时代的主流。20世纪50年代,由于数字电子技术的发展,使密钥流可以方便地利用以移位寄存器为基础的电路来产生,这促使线性和非线性移位寄存器理论迅速发展,加上有效的数学工具,使得流密码理论迅速发展和走向成熟。流密码本身也具有很多很优秀的性质,如计算简单、无错误传播等特点,这使得流密码在实际应用中,特别是在专用和机密机构中仍保持优势。实际上,在卫星通信等领域,流密码的使用依然是主流。

流密码是将明文划分成比特字符,字符分别与密钥流进行加密,解密时以同步产生同样的密钥流进行解密。其基本框图如下:

当然了,这图上有个小问题,密钥K的长度不能够太短… 不是因为好记而已,而是需要达到一定的长度来保证加密的安全性。如果真的想好记的话,可以通过第一部分介绍的标准Hash函数,将任意的子串Hash为密钥需要的长度。这样,在解密时先使用Hash将子串变为密钥后再进行运算。然而,这样的运算会降低加密的安全性(加密的安全性将变为min(单钥加密安全性, Hash抗碰撞安全性))。

分组密码

分组密码区别与流密码,是将明文消息编码表示后的子串划分等长的组,各组分别在各子密钥的控制下变成等长的输出序列,举例来说,AES分组加密算法的流程如下图所示:

分组密码是现在密码学研究的主流,因为其加密时可对明文和密文进行充分的混合,因此一般来说其安全性要比流密码高。当然了,现在的系统一般用分组密码产生密钥流,然后用密钥流和明文进行流加密,这就结合分组密码和流密码两种密码体制的优势。当然了,结合的结果是计算量会增加。

128位分组的AES分组密码的分组方法如图所示:


分组密码比较麻烦的地方在于补位。因为加密时,明文不一定是分组长度的整数倍,因此对于不够的地方需要一定的补位措施,而这种补位措施是需要公开的(否则补位方法也将成为密钥的一部分,这无疑增加了密钥的长度)。一般来说,填充方法如下。当明文不够分组长度的整数倍时:

当明文长度恰好为分组长度的整数倍时:

因此,实际上设计分组加密算法时,最后一个分组一般来说是攻击的突破口。举例来说,当明文的长度是分组长度的整数倍时,我们可以知道密文的最后一个分组所对应的明文就是0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF。因此,这就需要单钥加密体制有更高的安全性,即选择密文安全和选择明文安全。

单钥密码体制的工作模式

ECB模式

对一系列连续排列的消息进行加密的一个最直接的方式就是对它们逐个加密。这种加密方式类似于在电报密码本中指定码字,因此这个工作模式称为电码本模式。


电码本模式是确定性的。也就是说,如果在相同密钥下将一个消息加密两次,那么输出的密文也一定是相同的。在应用中,数据通常有部分可猜测的信息,例如薪水的数目就有一个可猜测的范围。如果明文消息是可猜测的,那么由确定性加密方案得到的密文就会使攻击者通过使用猜测的方法猜测出明文。因此,在大多数应用中不要使用ECB模式。

优点:

  1. 简单;
  2. 有利于并行计算;
  3. 误差不会被传送;

缺点:

  1. 不能隐藏明文的模式;
  2. 可能对明文进行主动攻击;

CBC模式

密码分组链接模式是用于一般数据加密的一个普通的分组密码算法。在这个模式中,第一个密文分组的计算需要一个特殊的明文,习惯上称之为初始向量(IV)。IV是一个随机的分组,每次会话加密时都要使用一个新的随机IV,IV无须保密,但一定是不可预知的。由于IV的随机性,IV将使得后续的密文分组都因为IV而随机化。由于IV需要公开,且第一个分组的加密结果是IV,因此CBC模式对于m个分组的明文将输出m+1个分组的密文。CBC模式如图所示:

优点:

  1. 不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。

缺点:

  1. 不利于并行计算;
  2. 误差传递;
  3. 需要初始化向量IV;

CFB模式

CFB模式与CBC模式类似,我们直接看流程图:

优点:

  1. 隐藏了明文模式;
  2. 分组密码转化为流模式;
  3. 可以及时加密传送小于分组的数据;

缺点:

  1. 不利于并行计算;
  2. 误差传递;
  3. 需要初始化向量IV;

OFB模式

OFB模式,我们直接看流程图:

优点:

  1. 隐藏了明文模式;
  2. 分组密码转化为流模式;
  3. 可以及时加密传送小于分组的数据;

缺点:

  1. 不利于并行计算;
  2. 误差传递;
  3. 对明文的主动攻击是可能的;

流密码算法

流密码算法我们只实现了RC4算法,实际上Bouncy Castle里面实现了所有已经公开的流密码算法,但是RC4算法依然是流密码算法的主流。我们的单钥加密函数有两部分:对于任意byte[]的加密,以及对于任意inputstream的加密。这里要注意的是,inputstream的加密我们使用了格式化输出,而非传统的将加密结果直接写入outputstream。这么做的原因是,对于不同操作系统,其编码方式有所不同。在Windows下面,如果打开一个byte[]文件,那么系统会自动对文件进行可行的转换操作,而这种转换会导致错误的解密结果。使用格式化输出后,所有的输出都被编码成0x00-0xFF,这样就避免了上述的问题。

加密算法实现如下:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.StreamCipher;
import org.bouncycastle.crypto.engines.RC4Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;

import cn.edu.buaa.crypto.algs.SymmetricBlockEnc.Mode;
import cn.edu.buaa.crypto.util.StdOut;

public class SymmetricStreamEnc {
	private static final int DEFUALT_BLOCK_SIZE = 128;
	
	public static byte[] enc_RC4(byte[] key, byte[] plaintext){
		// Make sure the validity of key, and plaintext
		assert (key != null && plaintext != null);
		KeyParameter kp = new KeyParameter(key);
		StreamCipher streamCipher = new RC4Engine();
		streamCipher.init(true, kp);

		byte[] ciphertext = new byte[plaintext.length];
		streamCipher.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
		return ciphertext;
	}
	
	public static byte[] dec_RC4(byte[] key, byte[] ciphertext){
		// Make sure the validity of key, and plaintext
		assert (key != null && ciphertext != null);
		KeyParameter kp = new KeyParameter(key);
		StreamCipher streamCipher = new RC4Engine();
		streamCipher.init(false, kp);

		byte[] plaintext = new byte[ciphertext.length];
		streamCipher.processBytes(ciphertext, 0, ciphertext.length, plaintext, 0);
		return plaintext;
	}
	
	public static void enc_RC4(byte[] key, InputStream in, OutputStream out){
		// Make sure the validity of key, and plaintext
		assert (key != null && in != null && out != null);
		KeyParameter kp = new KeyParameter(key);
		StreamCipher streamCipher = new RC4Engine();
		streamCipher.init(true, kp);
        try {
        	int inBlockSize = DEFUALT_BLOCK_SIZE;
    		int outBlockSize = DEFUALT_BLOCK_SIZE;
    		byte[] inblock = new byte[inBlockSize];
            byte[] outblock = new byte[outBlockSize];
            int inL;
            byte[] rv = null;
			while ((inL=in.read(inblock, 0, inBlockSize)) > 0)
			{
			    streamCipher.processBytes(inblock, 0, inL, outblock, 0);
			    rv = Hex.encode(outblock, 0, inL);
			    out.write(rv, 0, rv.length);
			    out.write('\n');
			}
		} catch (DataLengthException | IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void dec_RC4(byte[] key, InputStream in, OutputStream out){
		// Make sure the validity of key, and plaintext
		assert (key != null && in != null && out != null);
		KeyParameter kp = new KeyParameter(key);
		StreamCipher streamCipher = new RC4Engine();
		streamCipher.init(false, kp);
        try {
        	BufferedReader br = new BufferedReader(new InputStreamReader(in));
			byte[] inblock = null;
	        byte[] outblock = null;
	        String rv = null;
	        
	        while ((rv = br.readLine()) != null){
                inblock = Hex.decode(rv);
                outblock = new byte[DEFUALT_BLOCK_SIZE];
                streamCipher.processBytes(inblock, 0, inblock.length, outblock, 0);
                out.write(outblock, 0, inblock.length);
            }
		} catch (DataLengthException | IOException e) {
			e.printStackTrace();
		}
	}
}

对于测试,我们使用两个测试函数,Test_RC4_String以及Test_RC4_File。测试代码也可以作为使用的参考。

	public static void Test_RC4_String(){
		String key = "6206c34e2186e752c74e6df32ab8fa5b";
		StdOut.println("Test RC4.");
		String message = "Message";
		StdOut.println("Message = " + message);
		byte[] ciphertext = enc_RC4(Hex.decode(key), message.getBytes());
		StdOut.println("Encrypted Ciphertext = " + Hex.toHexString(ciphertext));
		String plaintext = new String(dec_RC4(Hex.decode(key), ciphertext));
		StdOut.println("Decrypted Plaintext = " + plaintext);
		StdOut.println();
	}
	
	public static void Test_RC4_File(){
		try {
			String key = "6206c34e2186e752c74e6df32ab8fa5b";
			File fileIn = new File("docs1.5on.zip");
			File fileEnc = new File("docs1.5on.enc");
			File fileDec = new File("docs1.5on.dec");
			FileInputStream in = new FileInputStream(fileIn);
			FileOutputStream out = new FileOutputStream(fileEnc);
			
			enc_RC4(Hex.decode(key), in, out);
			in.close();
			out.close();
			
			in = new FileInputStream(fileEnc);
			out = new FileOutputStream(fileDec);
			
			dec_RC4(Hex.decode(key), in, out);
			in.close();
			out.close();
		}  catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

分组密码算法

对于分组密码算法,我们只实现了AES(128bit, 192bit和256bit版本),我们实现了不同的工作模式。工作模式的控制由枚举类来完成。对于分组密码,其加密解密过程要稍微复杂一点,需要引入BufferedStream,不过大致的加密流程类似。代码如下:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.modes.OFBBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.encoders.Hex;

import cn.edu.buaa.crypto.util.StdOut;

public class SymmetricBlockEnc {
	public static final byte[] InitVector = { 0x38, 0x37, 0x36, 0x35, 0x34,
			0x33, 0x32, 0x31, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31 };

	public enum Mode {
		/**
		 * Electronic CodeBook Mode
		 */
		ECB,

		/**
		 * Cipher-Block Chaining Mode
		 */
		CBC,

		/**
		 * Cipher FeedBack Mode
		 */

		CFB,
		/**
		 * Output FeedBack Mode
		 */
		OFB,
	}

	// The default block size in bits (note: a multiple of 8)
	private static int DEFAULT_SIZE = 16;

	public static byte[] enc_AES(Mode mode, byte[] key, byte[] iv,
			byte[] plaintext) {
		// Make sure the validity of key, and plaintext
		assert (key != null && plaintext != null);
		// The valid key length is 16Bytes, 24Bytes or 32Bytes
		assert (key.length == 16 || key.length == 24 || key.length == 32);
		if (mode != Mode.ECB) {
			// The valid init vector is a no-none 16Bytes array
			assert (iv != null && iv.length == 16);
		}
		try {
			KeyParameter kp = new KeyParameter(key);
			BufferedBlockCipher b = null;
			switch (mode) {
			case ECB:
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(true, kp);
				break;
			case CBC:
				b = new PaddedBufferedBlockCipher(new CBCBlockCipher(
						new AESEngine()));
				b.init(true, new ParametersWithIV(kp, iv));
				break;
			case CFB:
				b = new PaddedBufferedBlockCipher(new CFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(true, new ParametersWithIV(kp, iv));
				break;
			case OFB:
				b = new PaddedBufferedBlockCipher(new OFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(true, new ParametersWithIV(kp, iv));
				break;
			default:
				// Default Mode is ECB Mode
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(true, kp);
				break;
			}
			byte[] enc = new byte[b.getOutputSize(plaintext.length)];
			int size1 = b.processBytes(plaintext, 0, plaintext.length, enc, 0);
			int size2;
			size2 = b.doFinal(enc, size1);
			byte[] ciphertext = new byte[size1 + size2];
			System.arraycopy(enc, 0, ciphertext, 0, ciphertext.length);
			return ciphertext;
		} catch (DataLengthException e) {
			e.printStackTrace();
			return null;
		} catch (IllegalStateException e) {
			e.printStackTrace();
			return null;
		} catch (InvalidCipherTextException e) {
			e.printStackTrace();
			return null;
		}
	}

	public static byte[] dec_AES(Mode mode, byte[] key, byte[] iv,
			byte[] ciphertext) {
		// Make sure the validity of key, and plaintext
		assert (key != null && ciphertext != null);
		// The valid key length is 16Bytes, 24Bytes or 32Bytes
		assert (key.length == 16 || key.length == 24 || key.length == 32);
		if (mode != Mode.ECB) {
			// The valid init vector is a no-none 16Bytes array
			assert (iv != null && iv.length == 16);
		}
		try {
			KeyParameter kp = new KeyParameter(key);
			BufferedBlockCipher b = null;
			switch (mode) {
			case ECB:
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(false, kp);
				break;
			case CBC:
				b = new PaddedBufferedBlockCipher(new CBCBlockCipher(
						new AESEngine()));
				b.init(false, new ParametersWithIV(kp, iv));
				break;
			case CFB:
				b = new PaddedBufferedBlockCipher(new CFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(false, new ParametersWithIV(kp, iv));
				break;
			case OFB:
				b = new PaddedBufferedBlockCipher(new OFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(false, new ParametersWithIV(kp, iv));
				break;
			default:
				// Default Mode is ECB Mode
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(false, kp);
				break;
			}
			byte[] dec = new byte[b.getOutputSize(ciphertext.length)];
			int size1 = b
					.processBytes(ciphertext, 0, ciphertext.length, dec, 0);
			int size2 = b.doFinal(dec, size1);
			byte[] plaintext = new byte[size1 + size2];
			System.arraycopy(dec, 0, plaintext, 0, plaintext.length);
			return plaintext;
		} catch (DataLengthException e) {
			e.printStackTrace();
			return null;
		} catch (IllegalStateException e) {
			e.printStackTrace();
			return null;
		} catch (InvalidCipherTextException e) {
			e.printStackTrace();
			return null;
		}
	}

	public static void enc_AES(Mode mode, byte[] key, byte[] iv,
			InputStream in, OutputStream out) {
		// Make sure the validity of key, and plaintext
		assert (key != null && in != null && out != null);
		// The valid key length is 16Bytes, 24Bytes or 32Bytes
		assert (key.length == 16 || key.length == 24 || key.length == 32);
		if (mode != Mode.ECB) {
			// The valid init vector is a no-none 16Bytes array
			assert (iv != null && iv.length == 16);
		}
		try {
			KeyParameter kp = new KeyParameter(key);
			BufferedBlockCipher b = null;
			switch (mode) {
			case ECB:
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(true, kp);
				break;
			case CBC:
				b = new PaddedBufferedBlockCipher(new CBCBlockCipher(
						new AESEngine()));
				b.init(true, new ParametersWithIV(kp, iv));
				break;
			case CFB:
				b = new PaddedBufferedBlockCipher(new CFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(true, new ParametersWithIV(kp, iv));
				break;
			case OFB:
				b = new PaddedBufferedBlockCipher(new OFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(true, new ParametersWithIV(kp, iv));
				break;
			default:
				// Default Mode is ECB Mode
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(true, kp);
				break;
			}
			int inBlockSize = b.getBlockSize() * 10;
			int outBlockSize = b.getOutputSize(inBlockSize);
			byte[] inblock = new byte[inBlockSize];
			byte[] outblock = new byte[outBlockSize];

			int inL;
			int outL;
			byte[] rv = null;

			while ((inL = in.read(inblock, 0, inBlockSize)) > 0) {
				outL = b.processBytes(inblock, 0, inL, outblock, 0);

				if (outL > 0) {
					rv = Hex.encode(outblock, 0, outL);

					out.write(rv, 0, rv.length);
					out.write('\n');
				}
			}

			outL = b.doFinal(outblock, 0);
			if (outL > 0) {
				rv = Hex.encode(outblock, 0, outL);
				out.write(rv, 0, rv.length);
				out.write('\n');
			}
		} catch (DataLengthException e) {
			e.printStackTrace();
			return;
		} catch (IllegalStateException e) {
			e.printStackTrace();
			return;
		} catch (InvalidCipherTextException e) {
			e.printStackTrace();
			return;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void dec_AES(Mode mode, byte[] key, byte[] iv,
			InputStream in, OutputStream out) {
		// Make sure the validity of key, and plaintext
		assert (key != null && in != null && out != null);
		// The valid key length is 16Bytes, 24Bytes or 32Bytes
		assert (key.length == 16 || key.length == 24 || key.length == 32);
		if (mode != Mode.ECB) {
			// The valid init vector is a no-none 16Bytes array
			assert (iv != null && iv.length == 16);
		}
		try {
			KeyParameter kp = new KeyParameter(key);
			BufferedBlockCipher b = null;
			switch (mode) {
			case ECB:
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(false, kp);
				break;
			case CBC:
				b = new PaddedBufferedBlockCipher(new CBCBlockCipher(
						new AESEngine()));
				b.init(false, new ParametersWithIV(kp, iv));
				break;
			case CFB:
				b = new PaddedBufferedBlockCipher(new CFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(false, new ParametersWithIV(kp, iv));
				break;
			case OFB:
				b = new PaddedBufferedBlockCipher(new OFBBlockCipher(
						new AESEngine(), DEFAULT_SIZE));
				b.init(false, new ParametersWithIV(kp, iv));
				break;
			default:
				// Default Mode is ECB Mode
				b = new PaddedBufferedBlockCipher(new AESEngine());
				b.init(false, kp);
				break;
			}
			BufferedReader br = new BufferedReader(new InputStreamReader(in));

			byte[] inblock = null;
			byte[] outblock = null;

			int outL;
			String rv = null;

			while ((rv = br.readLine()) != null) {
				inblock = Hex.decode(rv);
				outblock = new byte[b.getOutputSize(inblock.length)];

				outL = b.processBytes(inblock, 0, inblock.length, outblock, 0);
				if (outL > 0) {
					out.write(outblock, 0, outL);
				}
			}
			outL = b.doFinal(outblock, 0);
			if (outL > 0) {
				out.write(outblock, 0, outL);
			}
		} catch (DataLengthException e) {
			e.printStackTrace();
			return;
		} catch (IllegalStateException e) {
			e.printStackTrace();
			return;
		} catch (InvalidCipherTextException e) {
			e.printStackTrace();
			return;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

测试代码如下:

	private static void Test_AES_String() {
		String key = "6206c34e2186e752c74e6df32ab8fa5b";
		String iv = "00e5d201c2c2acbff8154861242ba0c4";
		String iv_p = "00e5d201c2c2acbff8154861242ba0c5";
		String message;
		byte[] ciphertext, ciphertext_p;
		String plaintext, plaintext_p;

		// Test ECB Mode
		StdOut.println("Test AES with ECB Mode.");
		message = "Message";
		StdOut.println("Message = " + message);
		ciphertext = enc_AES(Mode.ECB, Hex.decode(key), null,
				message.getBytes());
		StdOut.println("Encrypted Ciphertext = " + Hex.toHexString(ciphertext));
		plaintext = new String(dec_AES(Mode.ECB, Hex.decode(key), null,
				ciphertext));
		StdOut.println("Decrypted Plaintext = " + plaintext);
		StdOut.println();

		// Test CBC Mode
		StdOut.println("Test AES with CBC Mode.");
		message = "Message";
		StdOut.println("Message = " + message);
		// Test for Correctness
		ciphertext = enc_AES(Mode.CBC, Hex.decode(key), Hex.decode(iv),
				message.getBytes());
		StdOut.println("Encrypted Ciphertext = " + Hex.toHexString(ciphertext));
		plaintext = new String(dec_AES(Mode.CBC, Hex.decode(key),
				Hex.decode(iv), ciphertext));
		StdOut.println("Decrypted Plaintext = " + plaintext);
		// Test for Encryption with distinct IV
		ciphertext_p = enc_AES(Mode.CBC, Hex.decode(key), Hex.decode(iv_p),
				message.getBytes());
		StdOut.println("Encrypted Ciphertext = "
				+ Hex.toHexString(ciphertext_p));
		plaintext_p = new String(dec_AES(Mode.CBC, Hex.decode(key),
				Hex.decode(iv_p), ciphertext_p));
		StdOut.println("Decrypted Plaintext = " + plaintext_p);
		StdOut.println();

		// Test CFB Mode
		StdOut.println("Test AES with CFB Mode.");
		message = "Message";
		StdOut.println("Message = " + message);
		// Test for Correctness
		ciphertext = enc_AES(Mode.CFB, Hex.decode(key), Hex.decode(iv),
				message.getBytes());
		StdOut.println("Encrypted Ciphertext = " + Hex.toHexString(ciphertext));
		plaintext = new String(dec_AES(Mode.CFB, Hex.decode(key),
				Hex.decode(iv), ciphertext));
		StdOut.println("Decrypted Plaintext = " + plaintext);
		// Test for Encryption with distinct IV
		ciphertext_p = enc_AES(Mode.CFB, Hex.decode(key), Hex.decode(iv_p),
				message.getBytes());
		StdOut.println("Encrypted Ciphertext = "
				+ Hex.toHexString(ciphertext_p));
		plaintext_p = new String(dec_AES(Mode.CFB, Hex.decode(key),
				Hex.decode(iv_p), ciphertext_p));
		StdOut.println("Decrypted Plaintext = " + plaintext_p);
		StdOut.println();

		// Test OFB Mode
		StdOut.println("Test AES with OFB Mode.");
		message = "Message";
		StdOut.println("Message = " + message);
		// Test for Correctness
		ciphertext = enc_AES(Mode.OFB, Hex.decode(key), Hex.decode(iv),
				message.getBytes());
		StdOut.println("Encrypted Ciphertext = " + Hex.toHexString(ciphertext));
		plaintext = new String(dec_AES(Mode.OFB, Hex.decode(key),
				Hex.decode(iv), ciphertext));
		StdOut.println("Decrypted Plaintext = " + plaintext);
		// Test for Encryption with distinct IV
		ciphertext_p = enc_AES(Mode.OFB, Hex.decode(key), Hex.decode(iv_p),
				message.getBytes());
		StdOut.println("Encrypted Ciphertext = "
				+ Hex.toHexString(ciphertext_p));
		plaintext_p = new String(dec_AES(Mode.OFB, Hex.decode(key),
				Hex.decode(iv_p), ciphertext_p));
		StdOut.println("Decrypted Plaintext = " + plaintext_p);
		StdOut.println();
	}

	public static void Test_AES_File() {
		try {
			String key = "6206c34e2186e752c74e6df32ab8fa5b";
			String iv = "00e5d201c2c2acbff8154861242ba0c4";
			File fileIn = new File("docs1.5on.zip");
			File fileEnc = new File("docs1.5on.enc");
			File fileDec = new File("docs1.5on.dec");
			FileInputStream in = new FileInputStream(fileIn);
			FileOutputStream out = new FileOutputStream(fileEnc);

			enc_AES(Mode.CBC, Hex.decode(key), Hex.decode(iv), in, out);
			in.close();
			out.close();

			in = new FileInputStream(fileEnc);
			out = new FileOutputStream(fileDec);

			dec_AES(Mode.CBC, Hex.decode(key), Hex.decode(iv), in, out);
			in.close();
			out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}