题注
从博客中看出来我是个比较钟爱Java的应用密码学研究者。虽然C在密码学中有不可替代的优势:速度快,但是,Java的可移植性使得开发人员可以很快地将代码移植到各个平台,这比C实现要方便的多。尤其是Android平台的出现,Java的应用也就越来越广。因此,我本人在密码学研究过程中实际上也在逐渐使用和封装一些知名的Java密码学库,主要是方便自己使用。
Java JDK实际上自带了密码学库,支持几乎所有通用密码学原型的实现。然而,自带密码库有几个缺点:第一,由于版权问题,其并不支持全部的密码学原型,如256位的AES加密等等。第二,Java JDK的使用并非特别的方便。因此,我现在基本上在使用另一个知名的Java密码学库:Bouncy Castle,其官方网址为:http://www.bouncycastle.org/java.html。
但是,现在网上对于Bouncy Castle的介绍实在是有点少,而且每个人写的代码都不太一样,绝大多数人共享的代码只是为了个人使用,复用性并不是很强。因此,我现在在逐渐编写一些复用性比较强的封装,从而方便在自己设计的公钥加密、签名等体制中调用。
第一部分的实现是标准Hash算法。这一实现使用的是Java JDK,但是使用了Bouncy Castle的工具库实现Byte和String的一些转换。
一些Util封装
在进入正题前,我先介绍一下自己引用或者实现的一些Util封装,这些封装会方便我进行下一步的实现。不管使用的方便与否,各位看官凑合着看,凑合着用,如果有任何可以改进的地方还请给我发邮件,我的邮箱是footman_900217@126.com。
In.java
这个In.java,包括下面的Out.java、StdIn.java以及StdOut.java都是从Princeton的算法公开课中使用的库函数。他们对Java的原始输入输出进行了进一步的封装,我个人认为非常好用。当然了,在实现过程中大家也可以使用JDK自带的函数库。Princeton的开发者们实际上对于其库函数封装了一个统一的jar文件,称为stdlib.jar。大家可以直接在工程中引用这个jar文件,或者下载源代码并放置在工程中。我是选择了后者,这主要是为了方便工程向Android中的移植,避免了jar文件引用过程中可能出现的一些错误。所有的源代码可以从下面的链接中下载到:http://algs4.cs.princeton.edu/home/。为了方便使用,在此我附上代码,但是注意这些代码的原始版权为Princeton大学。
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import java.util.Scanner;
/**
* <i>Input</i>. This class provides methods for reading strings
* and numbers from standard input, file input, URL, and socket.
* <p>
* The Locale used is: language = English, country = US. This is consistent
* with the formatting conventions with Java floating-point literals,
* command-line arguments (via <tt>Double.parseDouble()</tt>)
* and standard output (via <tt>System.out.print()</tt>). It ensures that
* standard input works the number formatting used in the textbook.
* <p>
* For additional documentation, see <a href="http://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of
* <i>Introduction to Programming in Java: An Interdisciplinary Approach</i> by Robert Sedgewick and Kevin Wayne.
*/
public final class In {
private Scanner scanner;
// assume Unicode UTF-8 encoding
private final static String charsetName = "UTF-8";
// private final static String charsetName = "ISO-8859-1";
// assume language = English, country = US for consistency with System.out.
private Locale usLocale = new Locale("en", "US");
/**
* Create an input stream for standard input.
*/
public In() {
scanner = new Scanner(new BufferedInputStream(System.in), charsetName);
scanner.useLocale(usLocale);
}
/**
* Create an input stream from a socket.
*/
public In(Socket socket) {
try {
InputStream is = socket.getInputStream();
scanner = new Scanner(new BufferedInputStream(is), charsetName);
scanner.useLocale(usLocale);
}
catch (IOException ioe) {
System.err.println("Could not open " + socket);
}
}
/**
* Create an input stream from a URL.
*/
public In(URL url) {
try {
URLConnection site = url.openConnection();
InputStream is = site.getInputStream();
scanner = new Scanner(new BufferedInputStream(is), charsetName);
scanner.useLocale(usLocale);
}
catch (IOException ioe) {
System.err.println("Could not open " + url);
}
}
/**
* Create an input stream from a file.
*/
public In(File file) {
try {
scanner = new Scanner(file, charsetName);
scanner.useLocale(usLocale);
}
catch (IOException ioe) {
System.err.println("Could not open " + file);
}
}
/**
* Create an input stream from a filename or web page name.
*/
public In(String s) {
try {
// first try to read file from local file system
File file = new File(s);
if (file.exists()) {
scanner = new Scanner(file, charsetName);
scanner.useLocale(usLocale);
return;
}
// next try for files included in jar
URL url = getClass().getResource(s);
// or URL from web
if (url == null) { url = new URL(s); }
URLConnection site = url.openConnection();
InputStream is = site.getInputStream();
scanner = new Scanner(new BufferedInputStream(is), charsetName);
scanner.useLocale(usLocale);
}
catch (IOException ioe) {
System.err.println("Could not open " + s);
}
}
/**
* Does the input stream exist?
*/
public boolean exists() {
return scanner != null;
}
/**
* Is the input stream empty?
*/
public boolean isEmpty() {
return !scanner.hasNext();
}
/**
* Does the input stream have a next line?
*/
public boolean hasNextLine() {
return scanner.hasNextLine();
}
/**
* Read and return the next line.
*/
public String readLine() {
String line;
try { line = scanner.nextLine(); }
catch (Exception e) { line = null; }
return line;
}
/**
* Read and return the next character.
*/
public char readChar() {
// (?s) for DOTALL mode so . matches any character, including a line termination character
// 1 says look only one character ahead
// consider precompiling the pattern
String s = scanner.findWithinHorizon("(?s).", 1);
return s.charAt(0);
}
// return rest of input as string
/**
* Read and return the remainder of the input as a string.
*/
public String readAll() {
if (!scanner.hasNextLine()) { return null; }
// reference: http://weblogs.java.net/blog/pat/archive/2004/10/stupid_scanner_1.html
return scanner.useDelimiter("\\A").next();
}
/**
* Return the next string from the input stream.
*/
public String readString() {
return scanner.next();
}
/**
* Return the next int from the input stream.
*/
public int readInt() {
return scanner.nextInt();
}
/**
* Return the next double from the input stream.
*/
public double readDouble() {
return scanner.nextDouble();
}
/**
* Return the next float from the input stream.
*/
public double readFloat() {
return scanner.nextFloat();
}
/**
* Return the next long from the input stream.
*/
public long readLong() {
return scanner.nextLong();
}
/**
* Return the next byte from the input stream.
*/
public byte readByte() {
return scanner.nextByte();
}
/**
* Return the next boolean from the input stream, allowing "true" or "1"
* for true and "false" or "0" for false.
*/
public boolean readBoolean() {
String s = readString();
if (s.equalsIgnoreCase("true")) return true;
if (s.equalsIgnoreCase("false")) return false;
if (s.equals("1")) return true;
if (s.equals("0")) return false;
throw new java.util.InputMismatchException();
}
/**
* Read ints from file
*/
public static int[] readInts(String filename) {
In in = new In(filename);
String[] fields = in.readAll().trim().split("\\s+");
int[] vals = new int[fields.length];
for (int i = 0; i < fields.length; i++)
vals[i] = Integer.parseInt(fields[i]);
return vals;
}
/**
* Read doubles from file
*/
public static double[] readDoubles(String filename) {
In in = new In(filename);
String[] fields = in.readAll().trim().split("\\s+");
double[] vals = new double[fields.length];
for (int i = 0; i < fields.length; i++