1. 密码学简介 – 加密与解密
加密是一个将欲加密的资料用一些数学运算转成一团令人看不懂的东西的过程 ; 解密则是将加密文转换回原始文字的过程。这个过程中,扮演原始文字与加密文字之间转换的数学算法称为 Cipher 。
现代的 Cipher 多半会用 Key 来加密与解密资料。所谓 Key 是指一个机密值,我们可将它视为一通行密码。加密文字必需使用对映的 Key 才能解密为原始文字。
A. 对称型 Cipher
对称型 Cipher 在传送端与接收端所用的 Key 是一样的, 对称型 Cipher 又叫 Private Key Cipher ,因为 Key 的值只有传送端和接收端知道。如果有第三者知道了 Private Key 值,也就能解开加密的资料。
B. 非对称型 Cipher
非对称型的 Cipher 又叫 Public Key Cipher , Cipher 除了 Private Key 外,还会引进一可以随意散发的 Public Key 。被 Public Key 加密的资料只有相对映的 Private Key 可以解开,同样的被 Private Key 加密的资料也只有相对映的 Public Key 可以解开。
C. 讯息摘要 (Message Digest)
讯息摘要是从一组输入资料计算所得的一个特别数字,其原理运作就如 hash function 一般。在密码学的运用里,一般是用来验证资料是否被窜改。
2. JCE 下载
因为美国法规的限制, Sun 在 JDK 里只提供了少数的加密方法,其余大部份则只在 SunJCE 里提供,而且 SunJCE 的 API 限制只有美国、加拿大地区可以下载。表 1 为 Sun 及 SunJCE 分别支持的加密算法。
名称
型别
Sun
MD5
讯息摘要
SHA-1
讯息摘要
DSA
签章
SunJCE
HmacMD5
MAC
HmacSHA1
MAC
DES
对称型 Cipher
DESede
非对称型 Cipher
PBEWithMD5AndDES
对称型 Cipher
DH
Key 的交换
表 1 Sun 及 SunJCE 支持的加密算法
虽然美国法规有这样的限定,但是在美国境外也已经有厂商实作出 JCE ,并且可以在网络上直接下载,表 2 就是下载网址的列表。
套件
网址
免费
JCE
http://java.sun.com/products/jdk/1.2/jce/
是
Cryptix
http://www.cryptix.org/
是
IAIK
http://wwwjce.iaik.tu-graz.ac.at/
否
表 2 JCE 软件下载网址
3. JCE 安装
* 解压缩到 JDK 目录下
* Set ClassPath= C:\JDK\bin\cryptix-jce-api.jar;C:\JDK\bin\cryptix-jce-compat.jar;C:\JDK\bin\cryptix-jce-provider.jar …
* 在 JDK/lib/security/java.security 中加入
security.provider.1=sun.security.provider.Sun ( 原来就有的 )
security.provider.2=cryptix.jce.provider.Cryptix ( 加入 )
4. 程序范例
在举例之前,我先完成一个公用类别,用来将字符串转成十六进制表示法。
public class Msg {
public static String toHexString(byte[] b) {
StringBuffer hexString = new StringBuffer();
String plainText;
for (int i = 0; i < b.length; i++) {
plainText = Integer.toHexString(0xFF & b[i]);
if (plainText.length() < 2) {
plainText = "0" + plainText;
}
hexString.append(plainText);
}
return hexString.toString();
}
}
5. 讯息摘要 (message digest, 以 SHA1 为例 )
产生讯息摘要的步骤 :
* 呼叫 getInstance 取得 MessageDigest 实体
* 呼叫 update 将资料喂给 MessageDigest
* 呼叫 digest 产生讯息摘要
import java.security.*;
public class SHA extends Object {
public static void main(String[] args) throws Exception
{
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(args[0].getBytes());
byte[] digest = md.digest();
System.out.println(Msg.toHexString(digest));
}
}
ps. 比较两个讯息摘要是否相同时,可呼叫 isEqual 。
6. 讯息认证码 (MAC, 以 HmacSHA1 为例 )
讯息认证码只是在产生讯息摘要的过程里,加进一把 key 做为保护,目的是使讯息摘要更难被破解。
产生讯息认证码的步骤 :
* 利用密码产生一把 key
* 呼叫 getInstance 取得 Mac 实体
* 呼叫 init ,初始化 Mac
* 呼叫 update 喂资料给 Mac
* 呼叫 doFinal 产生讯息认证码
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class MacSHA {
public static void main(String[] args)
{
SecureRandom sr = new SecureRandom();
byte[] keyBytes = new byte[20];
sr.nextBytes(keyBytes);
SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA");
try {
Mac m = Mac.getInstance("HmacSHA");
m.init(key);
m.update(args[0].getBytes());
byte[] mac = m.doFinal();
System.out.println(Msg.toHexString(mac));
}
catch (Exception e) {
System.out.println("Exception!!");
}
}
}
7. 加密与解密 ( 以 DES 为例 )
这里举的加密 / 解密是属对称型 Cipher; 在金融交易里,常用对称型 Cipher 来加 / 解密资料。
加密 / 解密的步骤 :
* 利用密码产生一把 key
* 呼叫 getInstance 产生一个 Cipher 对象
* 呼叫 init 设定为加密或解密
* 加密 / 解密
import java.io.*;
import java.security.*;
import javax.crypto.*;
public class PwdDES {
public static final int kBufferSize = 8192;
public static void main(String[] args) throws Exception {
if (args.length < 4) {
System.out.println("Usage: java PwdDES -e|-d passwd inputfile outputfile");
return;
}
//Get or create key.
Key key;
KeyGenerator generator = KeyGenerator.getInstance("DES");
generator.init(new SecureRandom(args[1].getBytes()));
key = generator.generateKey();
//Get a cipher object
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS#5");
//Encrypt or decrypt
if (args[0].indexOf("e") != -1)
cipher.init(Cipher.ENCRYPT_MODE, key);
else
cipher.init(Cipher.DECRYPT_MODE, key);
FileInputStream in = new FileInputStream(args[2]);
FileOutputStream fileOut = new FileOutputStream(args[3]);
CipherOutputStream out = new CipherOutputStream(fileOut, cipher);
byte[] buffer = new byte[kBufferSize];
int length;
while ((length = in.read(buffer)) != -1)
out.write(buffer, 0, length);
in.close();
out.close();
}
}
8. 产生签章与认证 ( 以 DSA 为例 )
数字签章常用在网络上做个人身份确认。
产生签章的步骤 :
* 呼叫 getInstance 取得一个 Signature 实体
* 呼叫 initSign 初始化 Signature
* 呼叫 sign 产生签章
认证的步骤 :
* 呼叫 getInstance 取得一个 Signature 实体
* 呼叫 initVerify 初始化 Signature
* 呼叫 verify 认证
Sample1: 产生 Private/Public Key
import java.security.*;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.io.*;
public class KeyPair1 {
public static void main(String[] args)
{
try {
KeyPairGenerator genKeyPair = KeyPairGenerator.getInstance("DSA");
genKeyPair.initialize(1024, new SecureRandom());
KeyPair kpKey = genKeyPair.genKeyPair();
PrivateKey prKey = kpKey.getPrivate();
PublicKey puKey = kpKey.getPublic();
ObjectOutputStream osPrivate = new ObjectOutputStream(new FileOutputStream("D:\\Private.Key"));
ObjectOutputStream osPublic = new ObjectOutputStream(new FileOutputStream("D:\\Public.Key"));
osPrivate.writeObject(prKey);
osPublic.writeObject(puKey);
osPrivate.close();
osPublic.close();
}
catch (Exception e) {
System.out.println("Error");
}
}
}
Sample2: 产生签章与认证
import java.io.*;
import java.security.*;
import java.security.Signature;
import java.security.cert.*;
public class GenSign {
public static void main(String[] args) throws Exception {
String options = args[0];
String messagefile = args[1];
String signaturefile = args[2];
Signature signature = Signature.getInstance("DSA");
if (options.indexOf("s") != -1) {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:\\Private.key"));
PrivateKey priKey = (PrivateKey) is.readObject();
signature.initSign(priKey);
is.close();
}
else {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:\\Public.key"));
PublicKey pubKey = (PublicKey) is.readObject();
signature.initVerify(pubKey);
is.close();
}
FileInputStream in = new FileInputStream(messagefile);
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer))!= -1)
signature.update(buffer, 0, length);
in.close();
if (options.indexOf("s") != -1) {
FileOutputStream out = new FileOutputStream(signaturefile);
byte[] raw = signature.sign();
out.write(raw);
out.close();
}
else {
FileInputStream sigIn = new FileInputStream(signaturefile);
byte[] raw = new byte[sigIn.available()];
sigIn.read(raw);
sigIn.close();
if (signature.verify(raw))
System.out.println("The signature is good.");
else
System.out.println("The signature is bad.");
}
}
}
加密是一个将欲加密的资料用一些数学运算转成一团令人看不懂的东西的过程 ; 解密则是将加密文转换回原始文字的过程。这个过程中,扮演原始文字与加密文字之间转换的数学算法称为 Cipher 。
现代的 Cipher 多半会用 Key 来加密与解密资料。所谓 Key 是指一个机密值,我们可将它视为一通行密码。加密文字必需使用对映的 Key 才能解密为原始文字。
A. 对称型 Cipher
对称型 Cipher 在传送端与接收端所用的 Key 是一样的, 对称型 Cipher 又叫 Private Key Cipher ,因为 Key 的值只有传送端和接收端知道。如果有第三者知道了 Private Key 值,也就能解开加密的资料。
B. 非对称型 Cipher
非对称型的 Cipher 又叫 Public Key Cipher , Cipher 除了 Private Key 外,还会引进一可以随意散发的 Public Key 。被 Public Key 加密的资料只有相对映的 Private Key 可以解开,同样的被 Private Key 加密的资料也只有相对映的 Public Key 可以解开。
C. 讯息摘要 (Message Digest)
讯息摘要是从一组输入资料计算所得的一个特别数字,其原理运作就如 hash function 一般。在密码学的运用里,一般是用来验证资料是否被窜改。
2. JCE 下载
因为美国法规的限制, Sun 在 JDK 里只提供了少数的加密方法,其余大部份则只在 SunJCE 里提供,而且 SunJCE 的 API 限制只有美国、加拿大地区可以下载。表 1 为 Sun 及 SunJCE 分别支持的加密算法。
名称
型别
Sun
MD5
讯息摘要
SHA-1
讯息摘要
DSA
签章
SunJCE
HmacMD5
MAC
HmacSHA1
MAC
DES
对称型 Cipher
DESede
非对称型 Cipher
PBEWithMD5AndDES
对称型 Cipher
DH
Key 的交换
表 1 Sun 及 SunJCE 支持的加密算法
虽然美国法规有这样的限定,但是在美国境外也已经有厂商实作出 JCE ,并且可以在网络上直接下载,表 2 就是下载网址的列表。
套件
网址
免费
JCE
http://java.sun.com/products/jdk/1.2/jce/
是
Cryptix
http://www.cryptix.org/
是
IAIK
http://wwwjce.iaik.tu-graz.ac.at/
否
表 2 JCE 软件下载网址
3. JCE 安装
* 解压缩到 JDK 目录下
* Set ClassPath= C:\JDK\bin\cryptix-jce-api.jar;C:\JDK\bin\cryptix-jce-compat.jar;C:\JDK\bin\cryptix-jce-provider.jar …
* 在 JDK/lib/security/java.security 中加入
security.provider.1=sun.security.provider.Sun ( 原来就有的 )
security.provider.2=cryptix.jce.provider.Cryptix ( 加入 )
4. 程序范例
在举例之前,我先完成一个公用类别,用来将字符串转成十六进制表示法。
public class Msg {
public static String toHexString(byte[] b) {
StringBuffer hexString = new StringBuffer();
String plainText;
for (int i = 0; i < b.length; i++) {
plainText = Integer.toHexString(0xFF & b[i]);
if (plainText.length() < 2) {
plainText = "0" + plainText;
}
hexString.append(plainText);
}
return hexString.toString();
}
}
5. 讯息摘要 (message digest, 以 SHA1 为例 )
产生讯息摘要的步骤 :
* 呼叫 getInstance 取得 MessageDigest 实体
* 呼叫 update 将资料喂给 MessageDigest
* 呼叫 digest 产生讯息摘要
import java.security.*;
public class SHA extends Object {
public static void main(String[] args) throws Exception
{
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(args[0].getBytes());
byte[] digest = md.digest();
System.out.println(Msg.toHexString(digest));
}
}
ps. 比较两个讯息摘要是否相同时,可呼叫 isEqual 。
6. 讯息认证码 (MAC, 以 HmacSHA1 为例 )
讯息认证码只是在产生讯息摘要的过程里,加进一把 key 做为保护,目的是使讯息摘要更难被破解。
产生讯息认证码的步骤 :
* 利用密码产生一把 key
* 呼叫 getInstance 取得 Mac 实体
* 呼叫 init ,初始化 Mac
* 呼叫 update 喂资料给 Mac
* 呼叫 doFinal 产生讯息认证码
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class MacSHA {
public static void main(String[] args)
{
SecureRandom sr = new SecureRandom();
byte[] keyBytes = new byte[20];
sr.nextBytes(keyBytes);
SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA");
try {
Mac m = Mac.getInstance("HmacSHA");
m.init(key);
m.update(args[0].getBytes());
byte[] mac = m.doFinal();
System.out.println(Msg.toHexString(mac));
}
catch (Exception e) {
System.out.println("Exception!!");
}
}
}
7. 加密与解密 ( 以 DES 为例 )
这里举的加密 / 解密是属对称型 Cipher; 在金融交易里,常用对称型 Cipher 来加 / 解密资料。
加密 / 解密的步骤 :
* 利用密码产生一把 key
* 呼叫 getInstance 产生一个 Cipher 对象
* 呼叫 init 设定为加密或解密
* 加密 / 解密
import java.io.*;
import java.security.*;
import javax.crypto.*;
public class PwdDES {
public static final int kBufferSize = 8192;
public static void main(String[] args) throws Exception {
if (args.length < 4) {
System.out.println("Usage: java PwdDES -e|-d passwd inputfile outputfile");
return;
}
//Get or create key.
Key key;
KeyGenerator generator = KeyGenerator.getInstance("DES");
generator.init(new SecureRandom(args[1].getBytes()));
key = generator.generateKey();
//Get a cipher object
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS#5");
//Encrypt or decrypt
if (args[0].indexOf("e") != -1)
cipher.init(Cipher.ENCRYPT_MODE, key);
else
cipher.init(Cipher.DECRYPT_MODE, key);
FileInputStream in = new FileInputStream(args[2]);
FileOutputStream fileOut = new FileOutputStream(args[3]);
CipherOutputStream out = new CipherOutputStream(fileOut, cipher);
byte[] buffer = new byte[kBufferSize];
int length;
while ((length = in.read(buffer)) != -1)
out.write(buffer, 0, length);
in.close();
out.close();
}
}
8. 产生签章与认证 ( 以 DSA 为例 )
数字签章常用在网络上做个人身份确认。
产生签章的步骤 :
* 呼叫 getInstance 取得一个 Signature 实体
* 呼叫 initSign 初始化 Signature
* 呼叫 sign 产生签章
认证的步骤 :
* 呼叫 getInstance 取得一个 Signature 实体
* 呼叫 initVerify 初始化 Signature
* 呼叫 verify 认证
Sample1: 产生 Private/Public Key
import java.security.*;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.io.*;
public class KeyPair1 {
public static void main(String[] args)
{
try {
KeyPairGenerator genKeyPair = KeyPairGenerator.getInstance("DSA");
genKeyPair.initialize(1024, new SecureRandom());
KeyPair kpKey = genKeyPair.genKeyPair();
PrivateKey prKey = kpKey.getPrivate();
PublicKey puKey = kpKey.getPublic();
ObjectOutputStream osPrivate = new ObjectOutputStream(new FileOutputStream("D:\\Private.Key"));
ObjectOutputStream osPublic = new ObjectOutputStream(new FileOutputStream("D:\\Public.Key"));
osPrivate.writeObject(prKey);
osPublic.writeObject(puKey);
osPrivate.close();
osPublic.close();
}
catch (Exception e) {
System.out.println("Error");
}
}
}
Sample2: 产生签章与认证
import java.io.*;
import java.security.*;
import java.security.Signature;
import java.security.cert.*;
public class GenSign {
public static void main(String[] args) throws Exception {
String options = args[0];
String messagefile = args[1];
String signaturefile = args[2];
Signature signature = Signature.getInstance("DSA");
if (options.indexOf("s") != -1) {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:\\Private.key"));
PrivateKey priKey = (PrivateKey) is.readObject();
signature.initSign(priKey);
is.close();
}
else {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:\\Public.key"));
PublicKey pubKey = (PublicKey) is.readObject();
signature.initVerify(pubKey);
is.close();
}
FileInputStream in = new FileInputStream(messagefile);
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer))!= -1)
signature.update(buffer, 0, length);
in.close();
if (options.indexOf("s") != -1) {
FileOutputStream out = new FileOutputStream(signaturefile);
byte[] raw = signature.sign();
out.write(raw);
out.close();
}
else {
FileInputStream sigIn = new FileInputStream(signaturefile);
byte[] raw = new byte[sigIn.available()];
sigIn.read(raw);
sigIn.close();
if (signature.verify(raw))
System.out.println("The signature is good.");
else
System.out.println("The signature is bad.");
}
}
}