在工作中,我们用到了加密与解密,以及数字签名,所以对它的原理以及实现进行讲解:
实际上数字签名又称作基于PKI的电子签名, PKI的核心机构是电子认证服务提供者,即通称的认证机构CA,PKI签名的核心元素是由CA签发的数字证书,数字证书就如同日常生活中的身份证一样,用来标识网上实体的身份,CA就是对网上实体进行认证的第三方机构.数字证书就是CA机构对网上实体进行认证而产生的电子证书,它是数据签名的基础技术保障.CA机构对电子证书的有效性,有效期等提供查询服务.数字证书所包含公钥用来对使用对应的私钥加密的数据信息进行验证.
数字签名实现的具体原理是:
1、 将报文按双方约定的HASH算法计算得到一个固定位数的报文摘要。在数学上保证,只要改动报文中任何一位,重新计算出的报文摘要值就会与原先的值不相符。这样就保证了报文的不可更改性。
2、 将该报文摘要值用发送者的私人密钥加密,然后连同原报文和数字证书(包含公钥)一起发送给接收者而产生的报文即称数字签名。
3、接收方收到数字签名后,用同样的HASH算法对报文计算摘要值,然后与用发送者的公开密钥进行解密解开的报文摘要值相比较,如相等则说明报文确实来自所称的发送者。
4、同时通过证书颁发机构CA确认证书的有效性即可确认发送的真实身份。
数字证书的技术实现:
数字证书是公钥的载体,是PKI的核心元素.数字证书符合X.509标准,现行的PIK机制一般为又证书,即一个实体一般有两个证书,两个密码对,一个用于电子签名,一个用于加密通信.按照X.509标准,一个标准和数字证书的格式为:
CA《A》=CA{V,SN,AI,CA,UCA,A,UA,Ap,Ta}
它将包含证书颁发机构和标识及用户的标识,证书ID,有效期等信息(详见参考资料),另外还包含CA对此证书内容的进行了数字签名,以验证此证书的有效性.在验证一个数字证书的过程中,对数字证书的数字签名的验证将递归进行,只到找到双方共同可信任的CA根证书为止.
对于RSA产生的公钥、私钥,我们可以有两种方式可以对信息进行加密解密。私钥加密-公钥解密 和 公钥加密-私钥解密。
下面用个例子说明一下:
package edu.bjtu; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; class RSADemo { /** * 公钥 */ private RSAPublicKey publicKey; /** * 私钥 */ private RSAPrivateKey privateKey; /** * 用于加解密 */ private Cipher cipher; /** * 明文块的长度 它必须小于密文块的长度 - 11 */ private int originLength = 128; /** * 密文块的长度 */ private int encrytLength = 256; /** * 得到初始化的公钥和私钥 * * @throws NoSuchAlgorithmException * @throws NoSuchPaddingException */ public void initKey() throws NoSuchAlgorithmException, NoSuchPaddingException { // RSA加密算法:创建密钥对,长度采用2048 KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); kg.initialize(encrytLength * 8); KeyPair keypair = kg.generateKeyPair(); // 分别得到公钥和私钥 publicKey = (RSAPublicKey) keypair.getPublic(); privateKey = (RSAPrivateKey) keypair.getPrivate(); System.out.println("public key: " + publicKey); System.out.println("private key: " + privateKey); } /** * 将公钥保存至文件 * * @param file * 待写入的文件 * @return true 写入成功;false 写入失败 */ public boolean savePublicKey(File file) { return this.saveKey(publicKey, file); } /** * 将私钥保持至文件 * * @param file * 待写入的文件 * @return true 写入成功;false 写入失败 */ public boolean savePrivateKey(File file) { return this.saveKey(privateKey, file); } // 将Key文件保持到文件中 private boolean saveKey(Key key, File file) { boolean write; FileOutputStream fos = null; try { fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); // System.out.println(key.getFormat()); // 公钥默认使用的是X.509编码,私钥默认采用的是PKCS #8编码 byte[] encode = key.getEncoded(); // 注意,此处采用writeObject方法,读取时也要采用readObject方法 oos.writeObject(encode); write = true; } catch (IOException e) { write = false; } finally { try { if (fos != null) fos.close(); } catch (IOException e) { e.printStackTrace(); } } return write; } /** * 从文件中得到公钥 * * @param file * 保存公钥的文件 */ public void getPublicKey(File file) { getKey(file, 0); } /** * 从文件中得到私钥 * * @param file * 保存私钥的文件 */ public void getPrivateKey(File file) { getKey(file, 1); } private void getKey(File file, int mode) { FileInputStream fis; try { // 读取数据 fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); byte[] keybyte = (byte[]) ois.readObject(); // 关闭资源 ois.close(); // 默认编码 KeyFactory keyfactory = KeyFactory.getInstance("RSA"); // 得到公钥或是私钥 if (mode == 0) { X509EncodedKeySpec x509eks = new X509EncodedKeySpec(keybyte); publicKey = (RSAPublicKey) keyfactory.generatePublic(x509eks); // System.out.println(pk.getAlgorithm()); } else { PKCS8EncodedKeySpec pkcs8eks = new PKCS8EncodedKeySpec(keybyte); privateKey = (RSAPrivateKey) keyfactory .generatePrivate(pkcs8eks); // System.out.println(pk.getAlgorithm()); } } catch (IOException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeySpecException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 数据RSA加密 (用私匙加密) * * @param origin * 明文 * @return 密文 */ protected byte[] encrypt(byte[] origin) { // byte [] enc = new byte [encrytLength]; byte[] enc = null; try { cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); enc = cipher.doFinal(origin); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return enc; } /** * 数据RSA解密 (用公匙解密) * * @param enc * 密文 * @return 明文 */ protected byte[] decrypt(byte[] enc) { // byte [] origin = new byte [originLength]; byte[] origin = null; try { cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, publicKey); origin = cipher.doFinal(enc); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return origin; } /** * 加密文件 * * @param origin * 明文件 * @throws IOException */ public void encryptFile(File origin) throws IOException { FileInputStream fis = null; FileOutputStream fos = null; // 读入 fis = new FileInputStream(origin); BufferedInputStream bis = new BufferedInputStream(fis); byte[] originbyte = new byte[originLength]; // 写出 fos = new FileOutputStream(new File(origin + ".encrypt")); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] encryptbyte; // int k; while (bis.read(originbyte) > 0) { encryptbyte = this.encrypt(originbyte); bos.write(encryptbyte); originbyte = new byte[originLength]; } // 压入 bos.flush(); // 关闭资源 if (fis != null) fis.close(); if (fos != null) fos.close(); } /** * 解密文件 * * @param encrypt * 密文件 * @throws IOException */ public void decryptFile(File encrypt) throws IOException { FileInputStream fis = null; FileOutputStream fos = null; // 读入 fis = new FileInputStream(encrypt); BufferedInputStream bis = new BufferedInputStream(fis); byte[] encryptbyte = new byte[encrytLength]; // 写出 fos = new FileOutputStream(new File(encrypt + ".decrypt")); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] originbyte; // int k; while (bis.read(encryptbyte) > 0) { originbyte = this.decrypt(encryptbyte); bos.write(originbyte); encryptbyte = new byte[encrytLength]; } // 压入 bos.flush(); // 关闭资源 if (fis != null) fis.close(); if (fos != null) fos.close(); } }
package edu.bjtu; import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStreamWriter; /** * */ /********************************** * MD5 part **********************************/ } /** * MD5的算法在RFC1321 中定义 * 在RFC 1321中,给出了Test suite用来检验你的实现是否正确: * MD5 ("") = d41d8cd98f00b204e9800998ecf8427e * MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661 * MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72 * MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0 * MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b * * @author yilee * * 传入:一个字节数组 * 传出:字节数组的 MD5 结果字符串 */ class MD5Demo { public byte[] getFileBytes(String fileName) throws Exception { FileInputStream fis = new FileInputStream(fileName); BufferedInputStream bis = new BufferedInputStream(fis); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] data = new byte[4096]; int count = -1; while ((count = bis.read(data, 0, 4096)) != -1) { outStream.write(data, 0, count); } data = null; if(fis != null) fis.close(); return outStream.toByteArray(); } public void saveString2File(String content, String fileName) throws Exception { BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))); bw.write(content); bw.close(); } public String getMD5(byte[] source) { String s = null; char hexDigits[] = { // 用来将字节转换成 16 进制表示的字符 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; try { java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); md.update(source); byte tmp[] = md.digest(); // MD5 的计算结果是一个 128 位的长整数, // 用字节表示就是 16 个字节 char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符, // 所以表示成 16 进制需要 32 个字符 int k = 0; // 表示转换结果中对应的字符位置 for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节 // 转换成 16 进制字符的转换 byte byte0 = tmp[i]; // 取第 i 个字节 str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换, // >>> 为逻辑右移,将符号位一起右移 str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换 } s = new String(str); // 换后的结果转换为字符串 } catch (Exception e) { e.printStackTrace(); } return s; } }
package edu.bjtu; /** * RSA加密算法的演示验证 * RSA是一种分组加密算法 * 注意:密钥对采用的长度决定了加密块的长度,我这里取的是2048,即256byte * 由于加密块的长度固定为256,因此明文的长度至多为256 - 11 = 245byte * 我这里明文长度取的是128byte,密文长度为256byte,它适合于小文件加密 */ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStreamWriter; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.BadPaddingException; //import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; /** * 签名过程 */ public class Cipher { public static void main(String[] args) throws Exception { // 1. 第一步: 对原始内容进行散列(MD5算法) MD5Demo md5 = new MD5Demo(); byte[] srcContentBytes = md5.getFileBytes("d:\\test.txt"); // 获取原始内容字节流。test.txt是要签名的内容文件 String md5Str = md5.getMD5(srcContentBytes); // MD5散列 md5.saveString2File(md5Str, "d:\\test.md5.txt"); // 保存散列内容到文件 // 2. 第二步:得到私匙和公匙对 RSADemo rsa = new RSADemo(); rsa.initKey(); rsa.savePublicKey(new File("D:\\public.key")); rsa.savePrivateKey(new File("D:\\private.key")); // 3. 第三步:使用私匙对散列内容加密。并把加密后的内容保存到文件 “散列文件.encrypt” // 3.1 然后,加密者把 原始内容文件+加密后的散列内容文件+公匙 发给解密者 rsa.encryptFile(new File("D:\\test.md5.txt")); // 4. 解密者用公匙对“散列文件.encrypt”进行解密,得到解密后的散列内容,并保存到“散列文件.encrypt.decrypt” // 4.1 机密者对元素内容文件进行同样的散列,把得到的散列值和机密得到的散列值进行比对,如果一样,这任务元素内容是经过签名的,是可信的。 rsa.decryptFile(new File("D:\\test.md5.txt.encrypt")); } }