Java密碼學
(
史帝芬
, idealist@gcn.net.tw)
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.");
}
}
}