编码、散列与加解密
起因:前天去一家公司面试被问到数据加密的相关知识,完全回答不上来,回家后特地总结了一下。
一、编码、散列与加解密
编码:使用约定的协议对数据格式化。编码的反向操作是解码,双方并不需要专用密钥来获取真实数据。
散列:使用特定算法获取对象的数字摘要。散列是一种特殊算法,他人几乎无法伪造与原始数据完全相同的散列值,从而保证数据不会被恶意修改。但散列算法没有逆向操作,接收方不能通过散列值还原数据。
加密与解密:通过双方约定的密钥对信息加密。使用同一把密钥加解密的方式称为对称加密,使用不同密钥加解密的方式称为非对称加密。加密与编码类似只是计算过程必须有密钥参与,因此密钥是信息保密的关键。
二、编码与散列基础
编码与解码
import java.io.IOException; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; /** * 编码/解码 */ public class Base { // encrypt:byte[] to String public static String encryptBASE(byte[] src) { String dst = new BASE64Encoder().encode(src); return dst; } // decrypt:String to byte[] public static byte[] decryptBASE(String dst) throws IOException { byte[] src = new BASE64Decoder().decodeBuffer(dst); return src; } }
散列算法常见的规则有MD5和SHA两种
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * 散列 */ public class Digest { enum DIGEST_TYPE { MD5, SHA } public static byte[] hash(String src, DIGEST_TYPE type) throws NoSuchAlgorithmException { System.out.println(type); MessageDigest md = MessageDigest.getInstance(type.toString()); md.update(src.getBytes()); return md.digest(); } }
我们是如何保存用户密码的?
String password = "qwer1234"; // 生成摘要 byte[] digest = Digest.hash(password, DIGEST_TYPE.SHA); String coding = Base.encryptBASE(digest); System.out.println(coding); // 2yXy/BTNLSseevMHJB9Uj7A8MSo=
三、对称加密——DES
对称加密顾名思义就是双方持有同一把密钥,常用的对称加密算法是DES。
import java.io.IOException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; /** * DES对称加密 */ public class DES { public static final String ALGORITHM = "DES"; // byte[] to SecretKey private static Key getSecretKey(byte[] src) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException { // DES 密钥 DESKeySpec keySpec = new DESKeySpec(src); // 密钥的工厂 SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM); // 根据提供的密钥规范(密钥材料)生成 SecretKey 对象 return factory.generateSecret(keySpec); } // 获取一个能够生成SecretKey的字符串 public static String generateKey() throws NoSuchAlgorithmException { SecureRandom secureRandom = new SecureRandom(); // 此类提供(对称)密钥生成器的功能 KeyGenerator generatory = KeyGenerator.getInstance(ALGORITHM); // 初始化此密钥生成器 generatory.init(secureRandom); // 生成一个密钥 SecretKey secretKey = generatory.generateKey(); // 对密钥使用Base64编码器编码 return Base.encryptBASE(secretKey.getEncoded()); } // 加密 public static byte[] encrypt(byte[] src, String secretKey) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { Key key = getSecretKey(Base.decryptBASE(secretKey)); // 此类为加密和解密提供密码功能 Cipher cipher = Cipher.getInstance(ALGORITHM); // 用密钥初始化此 Cipher cipher.init(Cipher.ENCRYPT_MODE, key); // 按单部分操作加密或解密数据 return cipher.doFinal(src); } // 解密 public static byte[] decrypt(byte[] src, String secretKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, IOException, IllegalBlockSizeException, BadPaddingException { Key key = getSecretKey(Base.decryptBASE(secretKey)); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(src); } public static void main(String[] args) throws Exception { String inputStr = "DES"; String key = DES.generateKey(); System.err.println("原文:\t" + inputStr); System.err.println("密钥:\t" + key); byte[] outputData = DES.encrypt(inputStr.getBytes(), key); System.err.println("加密后:\t" + Base.encryptBASE(outputData)); outputData = DES.decrypt(outputData, key); System.err.println("解密后:\t" + new String(outputData)); } }
控制台输出:
原文: DES 密钥: /W3mroXN1RA= 加密后: Z1Z3eWISSKM= 解密后: DES
四、非对称加密——RSA
非对称加密实现逻辑要比对称加密复杂的多。首先服务器端需要生成一对公钥(PublicKey)与私钥(PrivateKey)——并持有私钥,将公钥发布出去供客户端使用。当发布数据时,服务器端利用私钥与数据生成“数字签名”(signature),该签名可以理解为发布者对应发布信息的数据摘要,从而禁止第三方伪造数据内容或篡改发布主体。接着使用私钥加密(encrypt)数据。客户端接收到数据后首先利用签名信息和公钥对密文(cryptograph)进行验证(verify),再通过公钥解密(decrypt)。而客户端向服务器发送请求的时候,首先利用公钥对原文(src)加密,服务器端则通过私钥解密。综上所述,整个流程至少应该包含以下方法:
- signature(byte[] src, String privateKey) // 生成数字签名
- verify(byte[] cryptograph, String signature, String publicKey) // 验证签名
- encryptByPteKey(byte[] src, String privateKey) // 使用私钥加密数据
- decryptByPubKey(byte[] cryptograph, String publicKey) // 使用公钥解密
- encryptByPubKey(byte[] src, String publicKey) // 使用公钥加密数据
- decryptByPteKey(byte[] cryptograph, String privateKey) // 使用私钥解密
下面是具体实现
import java.io.IOException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; 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; /** * RSA非对称加密 */ public class RSA { public static final String KEY_ALGORITHM = "RSA"; public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; private static PublicKey publicKey; private static PrivateKey privateKey; // 生成私钥和公钥 public static void generateKeys() throws NoSuchAlgorithmException { // KeyPairGenerator 类用于生成公钥和私钥对 KeyPairGenerator pairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); // 生成一个密钥对 KeyPair keyPair = pairGenerator.generateKeyPair(); // 返回对此密钥对的公钥组件的引用 publicKey = keyPair.getPublic(); // 返回对此密钥对的私钥组件的引用 privateKey = keyPair.getPrivate(); } // 获取公钥 public static String getPublicKey() { return Base.encryptBASE(publicKey.getEncoded()); } // 获取私钥 public static String getPrivateKey() { return Base.encryptBASE(privateKey.getEncoded()); } // 使用私钥对数据生成数字签名 public static String signature(byte[] src, String privateKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { byte[] key = Base.decryptBASE(privateKey); // PKCS8EncodedKeySpec类使用PKCS#8标准作为密钥规范管理的编码格式 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key); KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); // 根据提供的密钥规范(密钥材料)生成私钥对象 PrivateKey pteKey = factory.generatePrivate(spec); // Signature 类用来为应用程序提供数字签名算法 Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM); sign.initSign(pteKey); sign.update(src); return Base.encryptBASE(sign.sign()); } // 使用公钥与数字签名验证密文 public static boolean verify(byte[] cryptograph, String signature, String publicKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { byte[] key = Base.decryptBASE(publicKey); // 此类表示根据 ASN.1 类型 SubjectPublicKeyInfo 进行编码的公用密钥的 ASN.1 编码 X509EncodedKeySpec spec = new X509EncodedKeySpec(key); KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); // 根据提供的密钥规范(密钥材料)生成公钥对象 PublicKey pubKey = factory.generatePublic(spec); Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM); sign.initVerify(pubKey); sign.update(cryptograph); return sign.verify(Base.decryptBASE(signature)); } // 使用私钥加密 public static byte[] encryptByPteKey(byte[] src, String privateKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { byte[] key = Base.decryptBASE(privateKey); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key); KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); PrivateKey pteKey = factory.generatePrivate(spec); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, pteKey); return cipher.doFinal(src); } // 使用公钥解密 public static byte[] decryptByPubKey(byte[] cryptograph, String publicKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { byte[] key = Base.decryptBASE(publicKey); X509EncodedKeySpec spec = new X509EncodedKeySpec(key); KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); PublicKey pubKey = factory.generatePublic(spec); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, pubKey); return cipher.doFinal(cryptograph); } // 使用公钥加密 public static byte[] encryptByPubKey(byte[] src, String publicKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { byte[] key = Base.decryptBASE(publicKey); X509EncodedKeySpec spec = new X509EncodedKeySpec(key); KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); PublicKey pubKey = factory.generatePublic(spec); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, pubKey); return cipher.doFinal(src); } public static byte[] decryptByPteKey(byte[] cryptograph, String privateKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { byte[] key = Base.decryptBASE(privateKey); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key); KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); PrivateKey pteKey = factory.generatePrivate(spec); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, pteKey); return cipher.doFinal(cryptograph); } public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException { RSA.generateKeys(); String pubKey = RSA.getPublicKey(); String pteKey = RSA.getPrivateKey(); System.err.println("公钥: \n\r" + pubKey); System.err.println("私钥: \n\r" + pteKey); String inputStr = "Hello"; byte[] data = inputStr.getBytes(); byte[] encodedData = RSA.encryptByPteKey(data, pteKey); byte[] decryptData = RSA.decryptByPubKey(encodedData, pubKey); String outputStr = new String(decryptData); System.err.println("客户端获取信息: \n\r" + outputStr); inputStr = "Hi"; data = inputStr.getBytes(); encodedData = RSA.encryptByPubKey(data, pubKey); decryptData = RSA.decryptByPteKey(encodedData, pteKey); outputStr = new String(decryptData); System.err.println("服务器端获取信息: \n\r" + outputStr); } }
控制台输出:
公钥: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKf90/9LHg4YGigdQk/MaLSjciwW4NX76K3c5L NAr/TiZCYUDGWFiAgYIegK/Ymr0fqW0vA5hIULKkCTuP4FYn3DEWBxZ57OHKgy+BbVyiHcY7KWBC OSVijFWgSgjWg9BFiUQ2b63RIwbTpYEDVDzDYyqJUfqdh4/bVVr/4+WUbwIDAQAB
私钥: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMp/3T/0seDhgaKB1CT8xotKNyLB bg1fvordzks0Cv9OJkJhQMZYWICBgh6Ar9iavR+pbS8DmEhQsqQJO4/gVifcMRYHFnns4cqDL4Ft XKIdxjspYEI5JWKMVaBKCNaD0EWJRDZvrdEjBtOlgQNUPMNjKolR+p2Hj9tVWv/j5ZRvAgMBAAEC gYATliiNXhqyeL10PYCKj1SY9nW8y97cNk2U2v1wMrl5llKHCycbyEHPNDekwafAmL8ASAACkyNw ozWUPjxfn0BV8Jm5b90NWxXyyS5DJrm4DhvB3d+4e5B1YfxxvkVzvnc+J/R/jZAQ7ToAPQivyXvJ uUJgEEerHoho9jVFYOi6yQJBAOs47ORW7jRL/KWt0GKNx+laTX9hGH/SqHNdYEfX8aC2PupfdPw3 77PSJcLkcIlgsUT/guk+XCaS/mtkc/IpY30CQQDcYvovScZGCo/o2lK0G1mqosWoUTXy9G4n60wi sWX16+vrEtB4xCrNTjlfkMnKMLWNv2LdN191HlJzD3sYs8NbAkEA44jORlb81yPGAfIvyJXDkqwi mRwwWb1J60ahEv4FouOH2ql5/VySh4y5sFvPrGQXNlo/pSYId9vrNbEXI2H79QJAeozDaGZSzgHz kl1NHgATdXJ8DSPTpx1K4AHU3XneI8kj8B0PNgiHcJDuEHk37Kn3WzIwrKis+Th6SqcyIUNc/wJA Bp6Ep7qOqxce1I/SZc2lKYccLarjBJravkti6B0tBMKgiLU/iOS0UlUgx/n/De9C6rej0QihoUU6 UNwKlfg96A==
客户端获取信息: Hello 服务器端获取信息: Hi
后记:无