1
头图

Bytecode to text encoding method

In a computer, whether it is memory, disk, or network transmission, the data involved is stored or transmitted in a binary format.

Each binary bit (bit) can only be 0 or 1. Binary bits do not exist alone, but exist in the form of 8 binary bits forming a byte (byte), that is, 1 byte = 8 bit.

Bytecode cannot be directly converted into printable text characters. Sometimes you want to configure, store, and transmit a piece of binary bytecode through text, such as configuration files, HTML/XML, URL, e-mail body, HTTP Header, etc. Only text is supported In the scenario, it is necessary to convert the binary bytecode into a text string.

There are many ways to convert binary bytecode to text characters, the simplest way is to directly use 0 and 1 to represent. But in this case, only 8 0/1 characters can represent 1 byte, and the length is too long and it is inconvenient.

Two more compact methods are introduced below: HEX encoding and Base64 encoding.

HEX encoding

HEX is a hexadecimal encoding method, so it is also called Base16.

If the binary value in a byte is converted to hexadecimal and 16 characters of 0-9 and ae (ignoring case) are used, then each character can represent 4 binary digits (because of 2 to the 4th power). Equal to 16), then only two printable characters are needed to represent a byte.

Use HEX encoding in Java (depending on Apache Commons Codec ):

String str = "相对论";
byte[] bytes = str.getBytes("UTF-8");

// Hex 编码
String encodeString = Hex.encodeHexString(bytes);
System.out.println(encodeString); // 输出:e79bb8e5afb9e8aeba

// Hex 解码
byte[] decodeBytes = Hex.decodeHex(encodeString);
System.out.println(new String(decodeBytes, "UTF-8")); // 输出:相对论

HEX encoding is used in many scenarios. Here are several common usage scenarios:

RGB color code

RGB colors are usually expressed in HEX. For example, orange red can be represented by #FF4500:

.orangered { color: #FF4500; }

RGB refers to the three primary colors of red (red), green (green) and blue (blue). After these three colors are superimposed in different proportions, various colors can be obtained. The range of the intensity of each of the three colors is 0~255, and each requires 1 byte to represent, a total of 3 bytes.

The HEX code represents a certain RGB color, which is a string with a length of 6 digits (usually with # as a prefix, in this case the length is 7 digits). For example, #FF4500 means that the intensities of the three primary colors of red, green and blue are 255, 69, and 0 respectively.

URL encoding

Since only letters, numbers and some special symbols are allowed in the URL, when there are Chinese characters in the URL, it needs to be URL-encoded.

For example, the page URL of "Relativity" on Baidu Encyclopedia is: https://baike.baidu.com/item/%E7%9B%B8%E5%AF%B9%E8%AE%BA/48750

Among them, %E7%9B%B8%E5%AF%B9%E8%AE%BA actually encodes the three words'Relativity' with UTF-8 to obtain 9 bytes, and then use these 9 bytes separately The result of HEX encoding and prefixing with'%'.

IPv6 address

As IPv4 addresses are about to face the problem of not being enough, they will be replaced by IPv6. IPv6 uses 128 binary-bit addresses, which are usually represented by HEX encoding, for example:

2001:0db8:0000:0000:0000:ff00:0042:8329

Base64 encoding

If you feel that HEX encoding is not compact enough, then there is a more compact encoding method: Base64 encoding.

Base64 encoding uses a total of 64 characters to represent binary bits: 26 uppercase AZ, 26 lowercase az, 10 numbers 0-9, and 2 special symbols + and /. This means that each character can represent 6 binary bits, because 64 is equal to 2 to the 6th power.

Since each byte is 8 binary bits, and each character of Base64 encoding represents 6 binary bits, then 3 bytes (that is, 24 binary bits) can be made up, which can be encoded into 4 characters. If the number of bytes of the original data encoded by base64 is not a multiple of 3, then 1 or 2 bytes with a value of 0 will be added at the end, and Base64 encoding will be performed after the multiple of 3 is added. After encoding, 1 will be added at the end Or 2 = signs, indicating how many bytes have been added, which will be used in decoding.

Base64 encoding is used in Java:

String str = "相对论";
byte[] bytes = str.getBytes("UTF-8");

// Base64 编码
String encodeString = Base64.getEncoder().encodeToString(bytes);
System.out.println(encodeString); // 输出:55u45a+56K66

// Base64 解码
byte[] decodeBytes = Base64.getDecoder().decode(encodeString);
System.out.println(new String(decodeBytes, "UTF-8")); // 输出:相对论

There are also many usage scenarios for Base64 encoding. For example, because the picture file is not a text file, it cannot be directly written into HTML, and the result of Base64 encoding of the picture is a string of text, which can be directly put into HTML:

<img src="data:image/jpg;base64,/9j/4QMZR..." />

It should be noted that Base64 is not an encryption algorithm . Some developers use Base64 as an encryption algorithm, which is extremely insecure, because Base64 can be decoded by anyone without any key.

Message digest algorithm

Message-Digest Algorithm (Message-Digest Algorithm), also known as cryptographic hash function (CHF), can calculate a fixed-size result from bytecode data of any length through a hash algorithm. Commonly used message digest algorithms include MD5, SHA-1, SHA-2 series (including SHA-256, SHA-512, etc.).

Taking MD5 as an example, MD5 operation is performed on any piece of data, and the result is a hash value of 128 binary bits (16 bytes). The 32-bit MD5 string we see daily is actually the result of HEX encoding a 128-bit hash value.

For example, when using MD5 to perform operations on the string "Relativity" to get a 32-bit MD5 value, it actually goes through the following 3 steps (the following code relies on Apache Commons Codec ):

String str = "相对论";
// 1. 将字符串通过 UTF-8 编码转为字节数组
byte[] bytes = str.getBytes("UTF-8");
// 2. 对原始数组进行 MD5,得到一个 128 个二进制位(16 个字节)的哈希值
byte[] md5Bytes = DigestUtils.md5(bytes);
// 3. 将 128 位的哈希值 HEX 编码,得到一个长度为 32 的字符串
String md5Hex = Hex.encodeHexString(md5Bytes);
System.out.println(md5Hex); // 输出:fa913fb181bc1a69513e3d05a367da49

The above code is just to see the whole process of calculating the MD5 value of a string more clearly. In actual development, you can use a more convenient API, combining the above three steps into one step:

String str = "相对论";
// 使用默认的 UTF-8 编码将字符串转为字节数组计算 MD5 后再进行 HEX 编码
String md5Hex = DigestUtils.md5Hex(str);
System.out.println(md5Hex); // 输出:fa913fb181bc1a69513e3d05a367da49

In addition, the Apache Commons Codec in DigestUtils also provides a SHA-1, SHA-256, SHA-384, SHA-512 and other message digest algorithm.

The message digest algorithm has the following characteristics:

  • The result calculated by the message digest algorithm for the same message is always the same.
  • The results calculated by the message digest algorithm for different messages should be as different as possible. If the results after two different data message digests are the same, that is, a hash collision occurs, the greater the probability of hash collisions, the less secure the message digest algorithm will be.
  • Irreversible, the original data cannot be calculated backward through the hash result. Therefore, we generally think that the message digest algorithm is not an encryption algorithm because it cannot be decrypted. In addition, irreversibility here means that the operation is irreversible, but attackers usually use exhaustive methods or rainbow tables to find the original data corresponding to the hash value.

Here are some typical usage scenarios of message digest algorithms:

  • Use the message digest algorithm for the user's login password to obtain the hash value and then store it in the database. Even if the database is hacked and all the data is obtained, it is difficult to obtain the original value of the password. This is more secure than storing passwords in plain text. Of course, it is not safe to directly use the hash value storage, especially for some weak passwords, hackers can easily find the corresponding original value through the rainbow table. Therefore, the hash value is usually not stored directly, but undergoes some processing, such as salting, HMAC, and so on.
  • To compare whether the two files are the same, you only need to compare whether the message digests of the two files are the same, and you don't need to compare them one by one by byte. For example, Baidu Cloud Disk used to use the MD5 of the file to determine whether the newly uploaded file already exists. If it already exists, it does not need to be uploaded and stored repeatedly to save space.
  • Used for digital signature (Digital Signature), this will be introduced later in this article.

In scenarios with relatively high security requirements, MD5 and SHA-1 are not recommended at present, and the SHA-2 series algorithms are more commonly used.

HMAC

The full name of HMAC is Hash-Based Message Authentication Code, which adds a secret key to the message digest algorithm.

For example, HMAC-SHA256 adds a key to the SHA-256 algorithm. The following is a code example (depending on Apache Commons Codec ):

String str = "相对论";
String key = "12345678"; // 密钥
HmacUtils hmacUtils = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, key.getBytes("UTF-8"));
String result = hmacUtils.hmacHex(str.getBytes("UTF-8"));
System.out.println(result); // 输出:3bd7bbf58159a6d0bff846016b346a617a588fc1e9c43ebbdf38be53d3fc455a

Compared with the direct use of the message digest algorithm, the advantage of using HMAC is that it can verify the authenticity and integrity of the message.

As long as the key is not leaked, the hash value of the original data can be calculated and verified only if the key is held. Without the key, the attacker cannot send forged messages, nor can they tamper with the messages.

HMAC can be used for interface authentication. For example, for an HTTP interface exposed in a network environment, if you want to authenticate the caller, you can issue the key to the caller. When the caller is required to call the interface, use the key to calculate a signature through HMAC for all request parameters. The caller verifies the signature to ensure the authenticity and integrity of the request parameters.

In addition, because HMAC adds a key when calculating the hash value, compared to the direct use of the message digest algorithm, it is more difficult to be cracked by exhaustive methods and rainbow tables. User passwords are stored more securely after HMAC.

HMAC in JWT

A typical application scenario of HMAC is JWT. The full name of JWT is JSON Web Token.

Traditional authentication methods generally store the authenticated user information on the server, while JWT directly distributes the authenticated user information to the client for storage. Since the JWT is stored on the client side, anyone can forge or tamper with it. One way to solve this problem is that the server will sign the JWT token using HMAC and put the signature at the end of the token. Next time the client brings the JWT request, the server verifies whether the signature is correct. As long as the key is not leaked, the authenticity and integrity of the token can be guaranteed.

JWT token is divided into three parts:

  • Header: Header, specify the signature algorithm
  • Payload: Contains the main information transmitted by the token. This part can contain user information, such as user name, etc.
  • Signature: Signature, calculated as follows (secret is the key):

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)

Finally, these three parts are Base64 encoded and combined into a JWT token:

Encryption Algorithm

Encryption algorithms are divided into symmetric encryption algorithms and asymmetric encryption algorithms:

  • Symmetric-key cryptography: The same key is used for encryption and decryption. The most commonly used is the AES algorithm.
  • Asymmetric-key cryptography: Encryption and decryption use different keys. For example, public-key encrypted content can only be decrypted with a private key, so it is also called public-key cryptography. The most widely used is the RSA algorithm.

Symmetric encryption algorithm

Common symmetric encryption algorithms are DES, 3DES, and AES. The DES and 3DES standards have been gradually replaced by AES due to security issues.

AES has multiple modes of operation (mode of operation) and padding (padding):

  • Working modes: such as ECB, CBC, OFB, CFB, CTR, XTS, OCB, GCM, different mode parameters and encryption processes are different.
  • Filling method: Since AES is a block cipher algorithm, the original data will be split into 128-bit (16-byte) blocks by size during encryption. If the original data to be encrypted is not When it is an integer multiple of 16 bytes, it is necessary to fill the original data to make it an integer multiple of 16 bytes. Commonly used padding methods include PKCS5Padding, ISO10126Padding, etc. In addition, if you can ensure that the size of the original data to be encrypted is an integer multiple of 16 bytes, you can also choose not to pad, that is, NoPadding.

In actual work, it is necessary to encrypt and decrypt data across teams and languages. It often happens that after using one language to encrypt, another language cannot be decrypted. This is generally caused by the inconsistency of the working mode and filling method selected on both sides.

The following code uses ECB mode combined with PKCS5Padding padding as an example to encrypt and decrypt data:

public static byte[] encryptECB(byte[] data, byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
    byte[] result = cipher.doFinal(data);
    return result;
}

public static byte[] decryptECB(byte[] data, byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
    byte[] result = cipher.doFinal(data);
    return result;
}

public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
    String data = "Hello World"; // 待加密的明文
    String key = "12345678abcdefgh"; // key 长度只能是 16、25 或 32 字节

    byte[] ciphertext = encryptECB(data.getBytes(), key.getBytes());
    System.out.println("ECB 模式加密结果(Base64):" + Base64.getEncoder().encodeToString(ciphertext));

    byte[] plaintext = decryptECB(ciphertext, key.getBytes());
    System.out.println("解密结果:" + new String(plaintext));
}

Output:

ECB 模式加密结果(Base64):bB0gie8pCE2RBQoIAAIxeA==
解密结果:Hello World

Although the above ECB mode is simple and easy to use, it is not very safe. Since this mode encrypts each block independently, it will cause the same plaintext block to be encrypted into the same ciphertext block. The picture below is a good example:

In CBC mode, the concept of Initialization Vector (IV) is introduced to solve the problem of ECB mode.

The following is a code example of CBC mode combined with PKCS5Padding padding. Compared with ECB mode, there is an additional initial vector iv parameter during encryption and decryption:

public static byte[] encryptCBC(byte[] data, byte[] key, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
    byte[] result = cipher.doFinal(data);
    return result;
}

public static byte[] decryptCBC(byte[] data, byte[] key, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
    byte[] result = cipher.doFinal(data);
    return result;
}

public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
    String data = "Hello World"; // 待加密的原文
    String key = "12345678abcdefgh"; // key 长度只能是 16、25 或 32 字节
    String iv = "iviviviviviviviv"; // CBC 模式需要用到初始向量参数

    byte[] ciphertext = encryptCBC(data.getBytes(), key.getBytes(), iv.getBytes());
    System.out.println("CBC 模式加密结果(Base64):" + Base64.getEncoder().encodeToString(ciphertext));

    byte[] plaintext = decryptCBC(ciphertext, key.getBytes(), iv.getBytes());
    System.out.println("解密结果:" + new String(plaintext));
}

Output:

CBC 模式加密结果(Base64):K7bSB51+KxfqaMjJOsPAQg==
解密结果:Hello World

AES is widely used. It can be said that as long as you use the Internet, whether you are using a mobile phone APP or a Web application, almost all of them cannot do without the AES encryption algorithm. At present, most websites, including mobile APP back-end interfaces, have already used the HTTPS protocol, and most of the HTTPS uses the AES symmetric encryption algorithm in the data transmission stage.

However, the symmetric encryption algorithm represented by AES faces a problem, that is, how to transmit the key securely. Both parties that exchange data in the network need to use the same key for encryption and decryption. Once the key is exposed, the transmitted content is no longer secure. If the key itself needs to be transmitted, how to ensure security? For this problem, an asymmetric encryption algorithm is needed.

Asymmetric encryption algorithm

In 1977, Rivest, Shamir, and Adleman designed the RSA asymmetric encryption algorithm and won the Turing Award in 2002 (the highest international award in the computer field, known as the "Nobel Prize in the computer industry"). So far, the RSA algorithm has been the most widely used asymmetric encryption algorithm.

RSA has two keys: public key and private key.

The public key can be fully disclosed and anyone can obtain it. The private key is private and must not be leaked out.

The content encrypted by the public key can only be decrypted by the private key. The content encrypted by the private key can only be decrypted by the public key.

Based on the above rules, RSA has two different usages:

  • Public key encryption, private key decryption: the server exposes the public key, the client gets the public key, and transmits the data that it wants to transmit to the server through the public key encryption, then only the server can decrypt this data, because only the service The end has the private key, and even if any other intermediary gets the data during transmission, it can neither decrypt nor tamper with.
  • Private key signature, public key verification signature: The content publisher uses the message digest algorithm (such as SHA-256) to calculate the hash value of the published content, and then encrypts the hash value with the private key to obtain a signature, and add the signature to the publication Publish the content together. After other people get the content, they can decrypt the signature with the public key to get the hash value, and then compare whether the hash value is consistent with the hash value generated by the content to ensure that the content has not been tampered with. .

    Since it only verifies the authenticity and integrity of the data, there is no need to encrypt the entire content, only the hash value of the content is encrypted to verify, so the message digest algorithm is usually combined. For example, SHA256 with RSA signature is to use SHA-256 to calculate the hash value, and then use the RSA private key to encrypt.

The private key encryption and public key decryption mentioned above are only theoretically established. In fact, they will not be used directly in this way, but only for signatures. Because a piece of private key encrypted data, the decrypted public key is public, which means that anyone can decrypt it, so the encryption has no meaning.

Next, let's experience the RSA algorithm through Java code.

First, you need to generate a pair of public and private keys. The following uses the openssl command to generate a pair of public and private keys:

# 创建一个 PKCS#8 格式 2048 位的私钥
openssl genpkey -out private_key.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
# 通过私钥生成公钥
openssl pkey -in private_key.pem -pubout -out public_key.pem

The generated public key and private key are Base64 encoded text files, which can be opened directly with a text editor. Copy it to the following code to verify public key encryption, private key decryption, private key signature and public key verification signature:

public static void main(String[] args) throws Exception {
    String publicKeyBase64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0XYlulDsTzDWUb6X66Ia\n" +
            "giSn1dKriHvLHYth9hCcaGomdeIQahGnxzE1o76slEyS2HZ164QHqx8Za+LuT6IV\n" +
            "yLhU/ZNLWAZABe/sdNEkhti6vSSOdJE43KS4UVADeSgtN+7uXDuVgm35EPWZjkfV\n" +
            "5hiRX4nT5ALr1niyi1Ax4BWWyG4qX00n1HzY8MvoyiLdNob71qB+amjUNy9bDhcz\n" +
            "CDWtgA/ywOYU5Ec6vMgYfbAXPKGWwo318rS3UH8QtsO8iGcQbZ76q05LNEL8G3fo\n" +
            "0Kssj4fjrVGwSsyGztRRMLfGkW/hOPCDj82+D6dGQlGB3gyB7P1xVbkD67FujQA/\n" +
            "jwIDAQAB";
    String privateKeyBase64 = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDRdiW6UOxPMNZR\n" +
            "vpfrohqCJKfV0quIe8sdi2H2EJxoaiZ14hBqEafHMTWjvqyUTJLYdnXrhAerHxlr\n" +
            "4u5PohXIuFT9k0tYBkAF7+x00SSG2Lq9JI50kTjcpLhRUAN5KC037u5cO5WCbfkQ\n" +
            "9ZmOR9XmGJFfidPkAuvWeLKLUDHgFZbIbipfTSfUfNjwy+jKIt02hvvWoH5qaNQ3\n" +
            "L1sOFzMINa2AD/LA5hTkRzq8yBh9sBc8oZbCjfXytLdQfxC2w7yIZxBtnvqrTks0\n" +
            "Qvwbd+jQqyyPh+OtUbBKzIbO1FEwt8aRb+E48IOPzb4Pp0ZCUYHeDIHs/XFVuQPr\n" +
            "sW6NAD+PAgMBAAECggEABT96joJ8iTdmB0JJOCQlmeElO1w94/uGCUV2vN2JrawL\n" +
            "LqTtWFr84ya+e03JsSWCAF5ncfEq6AStdGCJLAGZnh/QMVJBbwEpFXz/ZaXfzmkb\n" +
            "tKV31D/XNuABpjfk/mIdT+tymWj8w/nRZbVhlYkDOPKgoc4oOuw/0G3Ru1/VABI+\n" +
            "yulNx93A/JNFGk3Bkm4E7jRWyl0BkAqAX2BZkFbXG/u3Jc0eYXrG74JfMH+MEihG\n" +
            "GDMSpBKNyX5zWkUT6XxpG82t2erHPWYEoNSoFzAUu+7rZ4ECEXxazAQclEHTkR3r\n" +
            "duUZ/XF0GL1WB0GC7+qvV/Z0gxjXuwG9oToFO/0MQQKBgQDu4DuTPWcYwSWY0R1f\n" +
            "qZUOuYRwD+5OQnJMIlKAD32QmvYT/jnvigjss5Qf1IUwf1UMynj2FnVF4D7L+kvq\n" +
            "O7LzYvHAeDQwZGGt2xWBlqjfhumlfBqfklkkqUiH2A5DvfvtbX/kkiY3n9C+oYZp\n" +
            "2ejiOtSC+NqQeB74TluxroEkvwKBgQDgehynybpFl4KkmDhgj++BH5RR+xzXIChb\n" +
            "gtIbbspdE1EyXy7Z9iNAJ8PVjHkSwh8iEfAO4EuJFnonF8UNIsWLr3gsKbQytRxR\n" +
            "cewqaBhTL54Vgl5dmODNrYjkZva5HHDsCLioYGgljdrj5e/gPSAWBrgT6kI+HypQ\n" +
            "/5xyp+KJMQKBgQCMxut1P8eliBa/M+YqvYdR8TVC0bCwwGoZwlR6kiZ+9UQ2zimY\n" +
            "qPHPhZmzFI0V4sTdz+lvphahAqIfljftKBezZklxE6Y2KsKCMk4/W+nUKe9Cjpwm\n" +
            "FJqih31uSX9Gnw18hH7N1u/c8juUTR8o/LpJsUASm9Q7Nf+SeKODWINVgwKBgDEx\n" +
            "UXpLsPBzRYQAf8pZgKkRXJWirC1QtMdpIdY1L0+6Xf7l8QR+9janADmaMSY1OFFl\n" +
            "EPCRorwGGvraMKqyRgxYhcNX2E+MdQo8Jv8cFMiWFNSt3zQvvoQUVX2IOuVSIET5\n" +
            "nE354pjoP2HWD/1aJ9/r1Qc4PRAUEFfzzDssI27hAoGAOsYKtvW6iRn/WVduIRcy\n" +
            "UtBRHHX0U16zGv+I7nOOBIYK5Uan6AjgzG2MfPOBj3cUhMMBDPfVg1cTbonw5Y8F\n" +
            "nSO4VLOtqKy0BRxCIUFqltJXUmj1zAJs84IweCBQ3un/OLVUMgE7qGtaIQy2PBsy\n" +
            "M8mwuUjo3Fu7l11E2Vgz/qY=";

    // Base64 解码
    byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64.replace("\n", ""));
    byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64.replace("\n", ""));

    // 生成 PublicKey(公钥) 和 PrivateKey(私钥) 对象
    PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
    PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));

    // 待加密、签名的原文
    String data = "Hello World";

    // 公钥加密,私钥解密
    byte[] ciphertext = encrypt(data.getBytes(), publicKey);
    System.out.println("RSA 公钥加密结果(Base64):" + Base64.getEncoder().encodeToString(ciphertext));
    byte[] plaintext = decrypt(ciphertext, privateKey);
    System.out.println("RSA 私钥解密结果:" + new String(plaintext));

    // 私钥签名,公钥验证签名
    byte[] signature = sign(data.getBytes(), privateKey);
    System.out.println("RSA 私钥签名结果(Base64):" + Base64.getEncoder().encodeToString(signature));
    boolean verifySuccess = verify(data.getBytes(), signature, publicKey);
    System.out.println("RSA 公钥验证签名结果:" + verifySuccess);
}

/**
 * 公钥加密
 */
public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    byte[] result = cipher.doFinal(data);
    return result;
}

/**
 * 私钥解密
 */
public static byte[] decrypt(byte[] data, PrivateKey privateKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] result = cipher.doFinal(data);
    return result;
}

/**
 * 私钥签名,使用 SHA256 with RSA 签名
 */
public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initSign(privateKey);
    signature.update(data);
    byte[] result = signature.sign();
    return result;
}

/**
 * 公钥验证签名
 */
public static boolean verify(byte[] data, byte[] sign, PublicKey publicKey) throws Exception {
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initVerify(publicKey);
    signature.update(data);
    return signature.verify(sign);
}

Output:

RSA 公钥加密结果(Base64):zoY6KM/RdCjAs7upJ9SIwqfXsSn3hAPu/z/ZPHbKgWN6+X0PpyVJVYT8jacEkzB7S2sJe/wLkO2TqXB2gqvL1AuDRgepVlxV2f6Uwx4DxM2/5RE0fAdTiICV5JEEIw81oLix0GGQ7nLjOhJxN9LaTJ2cXtwgR8gUtLtJ0tdWrxSMuN8FHLA45Nv8Ea1EAUQCvfanYZ2L39l++3/zBdg2wYQwCE6XGFnWnayUsGKYjC7JIufnq5f9VDL/kguLKceLmeTHqq31ccRTOQyhuoZjHCsbfXPlW2AT9ejgAcXy7LkXhYCfma50DBM+KUCfC4YrKBg6wKRqdZee90ZPcUKTkw==
RSA 私钥解密结果:Hello World
RSA 私钥签名结果(Base64):AbP5zSV/qvkF8fCseVkEaZMscvznQBUDtO3g0U/FIXVmzeR6WXFwPsMd3cC3oCHtnnqsL/aRQrpW6pHU6EzSJ5w6FgY6kD4kWREq9f8LOnyQm7CoS6CK0tUiAjIgG16rtmS+oPbG+mYaZkLzo1Cpkpz2MzuMMbWNivvXRMbj3wLiXyIMqUefawipvm+GPwrWRxesRot2sGtuZcxtMMZs3NHpJ0CXV/mQlYJWEzIiHUY4mqfqpMDL/djPf9td74ABpjk38O6r1Jt75TLnMvkwRdh7pHBQLZ0Tn/6Vx2cVD2D+sE9BuhinO66B6I0QOGVcl3a5C2whp+85zEovvdGlSg==
RSA 公钥验证签名结果:true

The HTTPS protocol, which can be seen everywhere, is based on the SSL/TLS protocol. In the SSL/TLS protocol, there is a handshake process before establishing an encrypted transmission channel. During the handshake process, the client will generate a random value, encrypted with the public key, and then sent to the server. This random value is used to generate the key of the symmetric encryption algorithm. Only the private key of the server can be decrypted, and no third party can decrypt it. This solves the security problem in the transmission process of the symmetric encryption algorithm mentioned above. In the communication phase after a successful handshake, a symmetric encryption algorithm is used for communication. Because the asymmetric encryption algorithm is more complicated, it is not efficient compared to the symmetric encryption algorithm, and it is not suitable for encryption and decryption of large amounts of data.

In addition, the digital certificate used in SSL/TLS will also be digitally signed by the CA in order to prevent forgery. Currently, most of the digital certificates used by HTTPS websites are signed with SHA256 with RSA.

For example, open https://xxgblog.com/ on the browser, click the small lock button on the left side of the address bar to view the certificate used by the website, the digital signature algorithm is SHA256 with RSA:


叉叉哥
3.8k 声望60 粉丝

引用和评论

0 条评论