无纸签名
在本章中,我们将研究数字签名,它是确定交换消息的通信方身份的第一个级别。我们将通过代码样本说明标识消息源的两种方法(一种比较难,另一种比较容易)。我们还将列出 JDK 1.4 支持的数字签名算法,并研究所涉及的类和方法。
您注意到什么是公钥密码术?中描述的公钥消息交换的缺陷了吗?Bob 怎么能够证实该消息确实是来自于 Alice 呢?Eve 可以用她的公钥替代 Alice 的公钥,然后 Bob 就会与 Eve 交换消息,并以为她就是 Alice。这被称为中间人(Man-in-the-Middle)攻击。
我们可以通过使用数字签名解决该问题 ― 数字签名是证实消息来自特定通信方的位模式。
实现数字签名的方法之一是逆用什么是公钥密码术?中描述的公钥过程。不是用公钥加密和用私钥解密,而是由发送方用私钥来对消息签名,然后接收方用发送方的公钥来解密消息。因为只有发送方才知道私钥,所以接收方可以确保消息确实是来自接收方。
实际上,消息摘要(什么是消息摘要?)(但并非整个消息)是用私钥签名的位流。因此,如果 Alice 想发送一条签名的消息给 Bob,她就生成该消息的消息摘要,然后用私钥对它签名。她将消息(以明文形式)和签名的消息摘要发送给 Bob。Bob 用 Alice 的公钥解密签名的消息摘要,然后计算明文消息的消息摘要并检查两个摘要是否匹配。如果它们匹配,则 Bob 可以确认消息来自于 Alice。
注:数字签名不提供消息加密,所以如果您还需要机密性,则必须将加密技术与签名结合使用。
您可以将 RSA 算法用于数字签名和加密。名为 DSA(数字签名算法 (Digital Signature Algorithm))的美国标准可以用于数字签名,但不可以用于加密。
JDK 1.4 支持下列数字签名算法:
- MD2/RSA
- MD5/RSA
- SHA1/DSA
- SHA1/RSA
我们将在本章中研究两个示例。首先研究困难的方法(请参阅数字签名代码示例:困难的方法 ),它使用我们已经讨论过的用于消息摘要和公钥密码术的原语来实现数字签名。然后研究简单的方法(请参阅数字签名代码示例:简单的方法),它使用 Java 语言对签名的直接支持。
import java.security.*;
import javax.crypto.*;
//
// This program demonstrates the digital signature technique at the
// primative level by generating a message digest of the plaintext
// and signing it with an RSA private key, to create the signature.
// To verify the signature, the message digest is again generated from
// the plaintext and compared with the decryption of the signature
// using the public key. If they match, the signature is verified.
public class DigitalSignature1Example {
public static void main (String[] args) throws Exception {
//
// check args and get plaintext
if (args.length !=1) {
System.err.println("Usage: java DigitalSignature1Example text");
System.exit(1);
}
byte[] plainText = args[0].getBytes("UTF8");
//
// get an MD5 message digest object and compute the plaintext digest
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
System.out.println( "\n" + messageDigest.getProvider().getInfo() );
messageDigest.update( plainText );
byte[] md = messageDigest.digest();
System.out.println( "\nDigest: " );
System.out.println( new String( md, "UTF8") );
//
// generate an RSA keypair
System.out.println( "\nStart generating RSA key" );
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair key = keyGen.generateKeyPair();
System.out.println( "Finish generating RSA key" );
//
// get an RSA cipher and list the provider
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
System.out.println( "\n" + cipher.getProvider().getInfo() );
//
// encrypt the message digest with the RSA private key
// to create the signature
System.out.println( "\nStart encryption" );
cipher.init(Cipher.ENCRYPT_MODE, key.getPrivate());
byte[] cipherText = cipher.doFinal(md);
System.out.println( "Finish encryption: " );
System.out.println( new String(cipherText, "UTF8") );
//
// to verify, start by decrypting the signature with the
// RSA private key
System.out.println( "\nStart decryption" );
cipher.init(Cipher.DECRYPT_MODE, key.getPublic());
byte[] newMD = cipher.doFinal(cipherText);
System.out.println( "Finish decryption: " );
System.out.println( new String(newMD, "UTF8") );
//
// then, recreate the message digest from the plaintext
// to simulate what a recipient must do
System.out.println( "\nStart signature verification" );
messageDigest.reset();
messageDigest.update(plainText);
byte[] oldMD = messageDigest.digest();
//
// verify that the two message digests match
int len = newMD.length;
if (len > oldMD.length) {
System.out.println( "Signature failed, length error");
System.exit(1);
}
for (int i = 0; i < len; ++i)
if (oldMD[i] != newMD[i]) {
System.out.println( "Signature failed, element error" );
System.exit(1);
}
System.out.println( "Signature verified" );
}
}
|
D:\IBM>java DigitalSignature1Example "This is a test!"
SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests
; SecureRandom; X.509 certificates; JKS keystore; PKIX CertPathValidator
; PKIX CertPathBuilder; LDAP, Collection CertStores)
Digest:
D647dbdek12*e,ad.?e
Start generating RSA key
Finish generating RSA key
BouncyCastle Security Provider v1.12
Start encryption
Finish encryption:
Akjsdfp-9q8237nrcas-9de8fn239-4rb[*[OPOsjkdfJDL:JF;lkjs;ldj
Start decryption
Finish decryption:
iNdf6D213$dcd(ndz!0)
Start signature verification
Signature verified
|
Signature
类使用由 KeyPairGenerator
类产生的密钥来操作数字签名。下面的示例中使用了下列方法:
-
KeyPairGenerator.getInstance("RSA")
、.initialize(1024)
和.generateKeyPair()
:生成密钥。
-
Cipher.getInstance("MD5WithRSA")
:创建Signature
对象。
-
.initSign(key.getPrivate())
:初始化Signature
对象。
-
.update(plainText)
和.sign()
:用明文字符串计算签名。
-
.initVerify(key.getPublic())
和.verify(signature)
:验证签名。
import java.security.*;
import javax.crypto.*;
//
// This example uses the digital signature features to generate and
// verify a signature much more easily than the previous example
public class DigitalSignature2Example {
public static void main (String[] args) throws Exception {
//
// check args and get plaintext
if (args.length !=1) {
System.err.println("Usage: java DigitalSignature1Example text");
System.exit(1);
}
byte[] plainText = args[0].getBytes("UTF8");
//
// generate an RSA keypair
System.out.println( "\nStart generating RSA key" );
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair key = keyGen.generateKeyPair();
System.out.println( "Finish generating RSA key" );
//
// get a signature object using the MD5 and RSA combo
// and sign the plaintext with the private key,
// listing the provider along the way
Signature sig = Signature.getInstance("MD5WithRSA");
sig.initSign(key.getPrivate());
sig.update(plainText);
byte[] signature = sig.sign();
System.out.println( sig.getProvider().getInfo() );
System.out.println( "\nSignature:" );
System.out.println( new String(signature, "UTF8") );
//
// verify the signature with the public key
System.out.println( "\nStart signature verification" );
sig.initVerify(key.getPublic());
sig.update(plainText);
try {
if (sig.verify(signature)) {
System.out.println( "Signature verified" );
} else System.out.println( "Signature failed" );
} catch (SignatureException se) {
System.out.println( "Signature failed" );
}
}
}
|
Start generating RSA key
Finish generating RSA key
Sun JSSE provider(implements RSA Signatures, PKCS12, SunX509 key/trust
factories, SSLv3, TLSv1)
Signature:
Ldkjahasdlkjfq[?owc42093nhasdk1a;sn;a#a;lksjd;fl@#kjas;ldjf78qwe09r7
Start signature verification
Signature verified |