余子越的博客
Toggle navigation
余子越的博客
主页
计算机网络
大数据分析
系统与工具
编程之路
容器引擎
作者
归档
标签
JAVA中的编码算法与加密算法
2020-12-19 00:12:57
14
0
0
yuziyue
[TOC] # 一. URL编码与解码 - 和标准的URL编码稍有不同,URLEncoder把空格字符编码成+,而现在的URL编码标准要求空格被编码为%20,不过,服务器都可以处理这两种情况。 ``` import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; String encoded = URLEncoder.encode("中 文!", StandardCharsets.UTF_8); System.out.println(encoded); String decoded = URLDecoder.decode("%E4%B8%AD+%E6%96%87%21", StandardCharsets.UTF_8); System.out.println(decoded); ``` # 二. Base64编码与解码 - Base64编码的目的是把二进制数据变成文本格式传输或处理,这样在很多文本中就可以处理二进制数据。 - Base64编码可以把任意长度的二进制数据变为纯文本,且只包含A~Z、a~z、0~9、+、/、=这些字符。 - 原理 - 把3字节的二进制数据按6bit一组,用4个int整数表示,然后查表,把int整数用索引对应到字符,得到编码后的字符串。 - 举个例子:3个byte数据分别是e4、b8、ad,按6bit分组得到39、0b、22和2d - 因为6位整数的范围总是0~63,所以,能用64个字符表示:字符A~Z对应索引0~25,字符a~z对应索引26~51,字符0~9对应索引52~61,最后两个索引62、63分别用字符+和/表示。 ``` ┌───────────────┬───────────────┬───────────────┐ │ e4 │ b8 │ ad │ └───────────────┴───────────────┴───────────────┘ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ │1│1│1│0│0│1│0│0│1│0│1│1│1│0│0│0│1│0│1│0│1│1│0│1│ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ┌───────────┬───────────┬───────────┬───────────┐ │ 39 │ 0b │ 22 │ 2d │ └───────────┴───────────┴───────────┴───────────┘ ``` ``` import java.util.Base64; byte[] input = "你好,world!".getBytes(StandardCharsets.UTF_8); String b64encoded = Base64.getEncoder().encodeToString(input); System.out.println(b64encoded); byte[] output = Base64.getDecoder().decode("5L2g5aW977yMd29ybGQh"); System.out.println(new String(output)); // 你好,world! ``` - 如果输入的byte[]数组长度不是3的整数倍,这种情况下,需要对输入的末尾补一个或两个0x00,编码后,在结尾加一个=表示补充了1个0x00,加两个=表示补充了2个0x00,解码的时候,去掉末尾补充的一个或两个0x00即可。 - 实际上,因为编码后的长度加上=总是4的倍数,所以即使不加=也可以计算出原始输入的byte[],java使用withoutPadding()去掉= ``` String b64encoded = Base64.getEncoder().withoutPadding().encodeToString(input); ``` # 三. 哈希算法 ## 3.1 使用hashCode String.hashCode方法返回int类型,4字节整数。 ``` String input = "你好,world!"; System.out.println(input.hashCode()); ``` ## 3.2 哈希加盐salt - 用哈希存储口令时要考虑彩虹表攻击。所以需要对每个口令额外拼接字符串。 ## 3.3 哈希碰撞 - 哈希碰撞是指,两个不同的输入得到了相同的输出 - 碰撞能不能避免?答案是不能。碰撞是一定会出现的,因为输出的字节长度是固定的,String的hashCode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。 - 一个安全的哈希算法必须满足 - 碰撞概率低 - 不能猜测输出,即能观察输入与输出的规律,从输出反推出输入。 - 根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。 - 常见的哈希算法有 | 算法 | 输出长度(位) | 输出长度(字节) | |-------------|----------|----------| | MD5 | 128 bits | 16 bytes | | RipeMD160 | 160 bits | 20 bytes | | SHA\-1 | 160 bits | 20 bytes | | SHA\-256 | 256 bits | 32 bytes | | SHA\-512 | 512 bits | 64 bytes | ## 3.4 内置哈希算法示例 - 针对不同的哈希算法,替换`MD5`为 `SHA-256` 算法名称即可。 ``` import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.math.BigInteger; MessageDigest m = MessageDigest.getInstance("MD5"); m.update("Hello".getBytes(StandardCharsets.UTF_8)); byte[] result = m.digest(); String md5 = new BigInteger(1, result).toString(16); System.out.println(md5); ``` ## 3.5 第三方哈希算法库 - 添加依赖 ``` <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.67</version> </dependency> ``` - 注册BouncyCastle,然后使用方法和内置使用方法一致。 ``` import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.math.BigInteger; import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; Security.addProvider(new BouncyCastleProvider()); MessageDigest m = MessageDigest.getInstance("RipeMD160"); m.update("Hello".getBytes(StandardCharsets.UTF_8)); byte[] result = m.digest(); String md5 = new BigInteger(1, result).toString(16); System.out.println(md5); ``` # 四. Hmac算法 ## 4.1 Hmac概述 - Hmac算法就是一种基于密钥的消息认证码算法,它的全称是Hash-based Message Authentication Code,是一种更安全的消息摘要算法。 - Hmac算法总是和某种哈希算法配合起来用的。例如,我们使用MD5算法,对应的就是HmacMD5算法,它相当于“加盐”的MD5 - Hmac算法可以简单的理解为MD5加salt的算法,但是更复杂,更安全。 - Hmac本质上就是把key混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供key。 <br> ## 4.2 Hmac示例 - 对字符串加密 - 生成随机secretKey,并将HelloWorld加密,base64编码是为了保存二进制,便于恢复成二进制。 - 将随机key:saltKey、加密后的key:encryptKey 存储数据库用于后期比对。 ``` KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5"); SecretKey secretKey = keyGen.generateKey(); String saltKey = Base64.getEncoder().encodeToString(secretKey.getEncoded()); System.out.println(saltKey); Mac mac = Mac.getInstance("HmacMD5"); mac.init(secretKey); mac.update("HelloWorld".getBytes(StandardCharsets.UTF_8)); String encryptKey = new BigInteger(1, mac.doFinal()).toString(16); System.out.println(encryptKey); ``` - 验证是否正确 - 使用存储的secretKey,并将 HelloWorld 加密,比较加密后的字符串是否一致。 ``` String saltKey = "Sb0pu6m4o89f6dcZKM1FN2TowuKtnGQxlu5w2Ro7iSrQ40eTVMVcaoOcq0plZLjPrZPQ9jwE+HlfZ9g5hG2ASA=="; SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode(saltKey), "HmacMD5"); Mac mac = Mac.getInstance("HmacMD5"); mac.init(secretKey); mac.update("HelloWorld".getBytes(StandardCharsets.UTF_8)); String encryptKey = new BigInteger(1, mac.doFinal()).toString(16); System.out.println(encryptKey); ``` <br> - 针对其他SHA算法,对应的名称如下 ``` .getInstance("HmacMD5"); .getInstance("HmacSHA1"); .getInstance("HmacSHA256"); .getInstance("HmacSHA512"); ``` # 五. 对称加密算法 从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文: ``` secret = encrypt(key, message); ``` 而解密则相反,它接收密码和密文,然后输出明文: ``` plain = decrypt(key, secret); ``` ## 5.1 AES ECB模式加密 - ECB模式是最简单的AES加密模式,它只需要一个固定长度的密钥,固定的明文会生成固定的密文,这种一对一的加密方式会导致安全性降低 ``` import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.Base64; public class Main { public static void main(String[] args) throws Exception { String message = "Hello, world!"; System.out.println("Message: " + message); // 长度为16字节的密码 byte[] key = "1234567890abcdef".getBytes(StandardCharsets.UTF_8); byte[] originData = message.getBytes(StandardCharsets.UTF_8); byte[] encrypted = encrypt(key, originData); System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted)); byte[] decrypted = decrypt(key, encrypted); System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8)); } // 加密: public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKey keySpec = new SecretKeySpec(key, "AES"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); return cipher.doFinal(input); } // 解密: public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKey keySpec = new SecretKeySpec(key, "AES"); cipher.init(Cipher.DECRYPT_MODE, keySpec); return cipher.doFinal(input); } } ``` ## 5.2 AES CBC模式加密 - 它需要一个随机数作为IV参数,这样对于同一份明文,每次生成的密文都不同。 - 在CBC模式下,需要一个随机生成的16字节IV参数,必须使用SecureRandom生成。 ``` import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.Base64; public class Main { public static void main(String[] args) throws Exception { String message = "Hello, world!"; System.out.println("Message: " + message); // 长度为32字节的密码 byte[] key = "1234567890abcdef1234567890abcdef".getBytes(StandardCharsets.UTF_8); byte[] originData = message.getBytes(StandardCharsets.UTF_8); byte[] encrypted = encrypt(key, originData); System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted)); byte[] decrypted = decrypt(key, encrypted); System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8)); } // 加密: public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); // CBC模式需要生成一个16 bytes的initialization vector: SecureRandom sr = SecureRandom.getInstanceStrong(); byte[] iv = sr.generateSeed(16); IvParameterSpec ivps = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps); byte[] data = cipher.doFinal(input); // IV不需要保密,把IV和密文一起返回: return join(iv, data); } // 解密: public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException { // 把input分割成IV和密文: byte[] iv = new byte[16]; byte[] data = new byte[input.length - 16]; System.arraycopy(input, 0, iv, 0, 16); System.arraycopy(input, 16, data, 0, data.length); // 解密: Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); IvParameterSpec ivps = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps); return cipher.doFinal(data); } public static byte[] join(byte[] bs1, byte[] bs2) { byte[] r = new byte[bs1.length + bs2.length]; System.arraycopy(bs1, 0, r, 0, bs1.length); System.arraycopy(bs2, 0, r, bs1.length, bs2.length); return r; } } ``` # 六. 口令加密算法 - 对称加密算法的秘钥长度都是固定的,二口令加密算法的秘钥可以是任意长度的。 - 用户输入的口令并不能直接作为AES的密钥进行加密,通常还需要使用PBE算法,采用随机数杂凑计算出真正的密钥,再进行加密,PBE就是Password Based Encryption的缩写。 - 在创建PBEParameterSpec的时候,还指定了循环次数1000,循环次数越多,暴力破解需要的计算量就越大。 - 下面以AES密钥为例。 ``` import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.security.Security; import java.util.Base64; public class Main { public static void main(String[] args) throws Exception { // 把BouncyCastle作为Provider添加到java.security Security.addProvider(new BouncyCastleProvider()); String message = "Hello, world!"; String password = "hello"; // 16 bytes随机Salt,该salt需要保存下来,在解码的时候使用 byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16); System.out.printf("salt: %032x\n", new BigInteger(1, salt)); byte[] data = message.getBytes(StandardCharsets.UTF_8); byte[] encrypted = encrypt(password, salt, data); System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted)); byte[] decrypted = decrypt(password, salt, encrypted); System.out.println("decrypted: " + new String(decrypted, StandardCharsets.UTF_8)); } // 加密: public static byte[] encrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException { PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); SecretKey skey = skeyFactory.generateSecret(keySpec); PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000); Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps); return cipher.doFinal(input); } // 解密: public static byte[] decrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException { PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); SecretKey skey = skeyFactory.generateSecret(keySpec); PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000); Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); cipher.init(Cipher.DECRYPT_MODE, skey, pbeps); return cipher.doFinal(input); } } ``` # 七. 密钥交换算法 - 解决的问题 对称加密算法解决了数据加密的问题。那现在如何传递密钥?在不安全的信道上传递加密文件是没有问题的,但是,如何在不安全的信道上安全地传输密钥?密钥交换算法即DH算法解决的就是这个问题。 - 假设现在甲乙要安全通讯,如果把a看成甲的私钥,A看成甲的公钥,b看成乙的私钥,B看成乙的公钥,DH算法的本质就是双方各自生成自己的私钥和公钥,私钥仅对自己可见,然后交换公钥,并根据自己的私钥和对方的公钥,生成最终的密钥secretKey,DH算法通过数学定律保证了双方各自计算出的secretKey是相同的。 ``` import javax.crypto.KeyAgreement; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.X509EncodedKeySpec; public class Main { public static void main(String[] args) { // Bob 和 Alice: Person bob = new Person("Bob"); Person alice = new Person("Alice"); // 各自生成KeyPair: bob.generateKeyPair(); alice.generateKeyPair(); // 双方交换各自的PublicKey: bob.generateSecretKey(alice.publicKey.getEncoded()); alice.generateSecretKey(bob.publicKey.getEncoded()); // 检查双方的本地密钥是否相同,双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加解密 bob.printKeys(); alice.printKeys(); } } class Person { public final String name; public PublicKey publicKey; private PrivateKey privateKey; private byte[] secretKey; public Person(String name) { this.name = name; } public void generateKeyPair() { try { KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH"); kpGen.initialize(512); KeyPair kp = kpGen.generateKeyPair(); this.privateKey = kp.getPrivate(); this.publicKey = kp.getPublic(); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } public void generateSecretKey(byte[] receivedPubKeyBytes) { try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPubKeyBytes); KeyFactory kf = KeyFactory.getInstance("DH"); PublicKey receivedPublicKey = kf.generatePublic(keySpec); KeyAgreement keyAgreement = KeyAgreement.getInstance("DH"); keyAgreement.init(this.privateKey); keyAgreement.doPhase(receivedPublicKey, true); this.secretKey = keyAgreement.generateSecret(); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } public void printKeys() { System.out.printf("Name: %s\n", this.name); System.out.printf("Private key: %x\n", new BigInteger(1, this.privateKey.getEncoded())); System.out.printf("Public key: %x\n", new BigInteger(1, this.publicKey.getEncoded())); System.out.printf("Secret key: %x\n", new BigInteger(1, this.secretKey)); } } ``` # 八. 非对称加密算法 - 非对称加密就是加密和解密使用的不是相同的密钥,只有同一个公钥--私钥对才能正常加解密,公钥用于加密,私钥用于解密。 - 非对称加密的典型算法就是RSA算法,它是由Ron Rivest,Adi Shamir,Leonard Adleman三人共同发明,所以使用三人的首字母。 - 非对称加密解密场景 - A和B要传输数据,首先A和B分别生成自己的公钥--私钥对,公钥对外开放,私钥自己保存。 - 1、A要发送数据给B:A向B索取B的公钥,然后用B的公钥加密待传输的数据,加密完成后就可以发送给B了。B收到加密后的数据后,用自己的私钥进行解密即可。 - 2、B要发送数据给A:同理,B需要A的公钥加密数据,然后传输给A,A自己的秘钥解密即可。 - 非对称加密解密的缺点 - 运算速度非常慢,比对称加密要慢很多 <br> - 在实际应用时,非对称加密总是和对称加密一起使用。假设A需要给B传输加密文件,首先A和B交换了各自的公钥,然后. - A生成一个随机的AES口令,然后用B的公钥通过RSA加密这个口令,并发给A; - A用自己的RSA私钥解密得到AES口令; - 双方后续就使用这个共享的AES口令用AES加密通信(对称加密)。 <br> - 可见非对称加密实际上应用在第一步,即加密AES口令。这也是浏览器中常用的HTTPS协议的做法,即浏览器和服务器先通过RSA非对称加密方式获取AES口令,接下来双方通信实际上采用的是速度较快的AES对称加密,而不是缓慢的RSA非对称加密。 <br> - 下面是使用非对称加密的示例 - 以RSA算法为例,它的密钥有256/512/1024/2048/4096等不同的长度。长度越长,密码强度越大,当然计算速度也越慢。 - 只使用非对称加密算法不能防止中间人攻击。 ``` import javax.crypto.Cipher; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; public class Main { public static void main(String[] args) throws Exception { byte[] plain = "Hello, World!".getBytes(StandardCharsets.UTF_8); Person alice = new Person("alice"); // 用 Alice 的公钥加密 byte[] pk = alice.getPublicKey(); System.out.printf("public key: %x%n", new BigInteger(1, pk)); byte[] encrypted = alice.encrypt(plain); System.out.printf("encrypted: %x%n", new BigInteger(1, encrypted)); // 用 Alice 的私钥解密: byte[] sk = alice.getPrivateKey(); System.out.printf("private key: %x%n", new BigInteger(1, sk)); byte[] decrypted = alice.decrypt(encrypted); System.out.println(new String(decrypted, StandardCharsets.UTF_8)); } } class Person { String name; PrivateKey sk; PublicKey pk; // 创建公钥--私钥对 public Person(String name) throws GeneralSecurityException, IOException { this.name = name; File priFile = Paths.get("/tmp/", this.name + ".pri").toFile(); File pubFile = Paths.get("/tmp/", this.name + ".pub").toFile(); if (priFile.exists() && pubFile.exists()) { // 如果之前创建过公钥--私钥对,直接从文件读取。 KeyFactory kf = KeyFactory.getInstance("RSA"); try (InputStream pri = new FileInputStream(Paths.get("/tmp/", this.name + ".pri").toFile()); InputStream pub = new FileInputStream(Paths.get("/tmp/", this.name + ".pub").toFile())){ PKCS8EncodedKeySpec priSpec = new PKCS8EncodedKeySpec(pri.readAllBytes()); this.sk = kf.generatePrivate(priSpec); X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pub.readAllBytes()); this.pk = kf.generatePublic(pubSpec); } } else { // 如果没有创建过公钥--私钥对,则创建新的。 KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA"); kpGen.initialize(1024); KeyPair kp = kpGen.generateKeyPair(); this.sk = kp.getPrivate(); this.pk = kp.getPublic(); this.saveKey(); } } // 把私钥导出为字节 public byte[] getPrivateKey() { return this.sk.getEncoded(); } // 把公钥导出为字节 public byte[] getPublicKey() { return this.pk.getEncoded(); } // 用公钥加密: public byte[] encrypt(byte[] message) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, this.pk); return cipher.doFinal(message); } // 用私钥解密: public byte[] decrypt(byte[] input) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, this.sk); return cipher.doFinal(input); } // 保存公钥 和 私钥到文件 public void saveKey() throws IOException { try (OutputStream pri = new FileOutputStream(Paths.get("/tmp/", this.name + ".pri").toFile()); OutputStream pub = new FileOutputStream(Paths.get("/tmp/", this.name + ".pub").toFile())){ pri.write(this.sk.getEncoded()); pub.write(this.pk.getEncoded()); } } } ``` # 九. 签名算法 - 使用非对称加密算法的时候,对于一个公钥-私钥对,通常是用公钥加密,私钥解密。 <br> - 如果使用私钥加密,公钥解密,公钥对外开放,私钥自己保存,这个就是所谓的签名算法,它的意义在于为了确认某个信息确实是由某个发送方发送的,任何人都不可能伪造消息。 <br> - 私钥加密得到的密文实际上就是数字签名,要验证这个签名是否正确,只能用私钥持有者的公钥进行解密验证。 <br> - 在实际应用的时候,签名实际上并不是针对原始消息,而是针对原始消息的哈希进行签名。在验证签名时,用公钥解密签名得到哈希值,将该哈希值和原始消息的哈希值进行比较。 <br> - 用户总是使用自己的私钥进行签名,所以,私钥就相当于用户身份。而公钥用来给外部验证用户身份。 - 常用数字签名算法有: - MD5withRSA - SHA1withRSA - SHA256withRSA ``` import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; public class Main { public static void main(String[] args) throws GeneralSecurityException { // 生成RSA公钥/私钥 KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA"); kpGen.initialize(1024); KeyPair kp = kpGen.generateKeyPair(); PrivateKey sk = kp.getPrivate(); PublicKey pk = kp.getPublic(); // 待签名的消息 byte[] message = "Hello, I am Bob!".getBytes(StandardCharsets.UTF_8); // 用私钥签名 Signature s = Signature.getInstance("SHA1withRSA"); s.initSign(sk); s.update(message); byte[] signed = s.sign(); System.out.printf("signature: %x%n", new BigInteger(1, signed)); // 用公钥验证 Signature v = Signature.getInstance("SHA1withRSA"); v.initVerify(pk); v.update(message); boolean valid = v.verify(signed); System.out.println("valid? " + valid); } } ``` # 十. 数字证书 - 我们知道,摘要算法(哈希算法)用来确保数据没有被篡改,非对称加密算法可以对数据进行加密解密,签名算法可以确保数据完整性和抗否认性,把这些算法集合到一起,并搞一套完善的标准,这就是数字证书。 <br> - 因此,数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种安全标准。 <br> - 以HTTPS协议为例,浏览器和服务器建立安全连接的步骤如下: - 浏览器向服务器发起请求,服务器向浏览器发送自己的数字证书; - 浏览器用操作系统内置的Root CA来验证服务器的证书是否有效,如果有效,就使用该证书加密一个随机的AES口令并发送给服务器; - 服务器用自己的私钥解密获得AES口令,并在后续通讯中使用AES加密。 - 上述流程只是一种最常见的单向验证。如果服务器还要验证客户端,那么客户端也需要把自己的证书发送给服务器验证,这种场景常见于网银等。 <br><br><br> - 参考资料 https://www.liaoxuefeng.com/wiki/1252599548343744/1255943717668160 <br><br><br>
上一篇:
JAVA线程基础
下一篇:
JAVA正则表达式使用总结
0
赞
14 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
文档导航