(Give a star to algorithm enthusiasts, train your programming skills)
Source: Zero One Technology Stack
blog.csdn.net/baidu_22254181/article/details/82594072
Introduction
Digital signatures and information encryption are technologies frequently used in both front-end and back-end development, with application scenarios including user login, transactions, information communication, oauth
, etc. Different application scenarios may require the use of different signature encryption algorithms or need to pair different signature encryption algorithms to achieve business goals. Here, I will briefly introduce several common signature encryption algorithms and their applications in typical scenarios.
Main Content
1. Digital Signature
Digital signatures are, simply put, a way to verify one’s identity by providing verifiable digital information. A set of digital signatures typically defines two complementary operations, one for signing and the other for verification. The sender holds a private key that represents their identity (the private key must not be disclosed), while the receiver holds the corresponding public key, which can be used to verify the sender’s identity when receiving information from them.

Note: The encryption process shown in the figure differs from public key encryption, more details here: https://www.zhihu.com/question/25912483. The fundamental purpose of signatures is to uniquely prove the identity of the sender, preventing man-in-the-middle attacks and
CSRF
cross-domain identity forgery. Based on this, signature algorithms are used in various authentication systems such as device authentication, user authentication, and third-party authentication (the implementation methods may differ).
2. Encryption and Decryption
2.1. Encryption
The basic process of data encryption is to process files or data originally in plaintext using some algorithm to make them into unreadable code, usually referred to as ciphertext. This approach aims to protect data from being illegally accessed or read.
2.2. Decryption
The reverse process of encryption is decryption, which is the process of converting the encoded information back to its original data.
3. Symmetric and Asymmetric Encryption
Encryption algorithms are divided into symmetric encryption and asymmetric encryption. In symmetric encryption algorithms, the encryption and decryption keys are the same, while in asymmetric encryption algorithms, the encryption key and the decryption key are different. Additionally, there is a type of hash algorithm that does not require a key.
Common symmetric encryption algorithms include
DES
,3DES
,AES
, etc., while common asymmetric algorithms includeRSA
,DSA
, etc. Hash algorithms mainly includeSHA-1
,MD5
, etc.
3.1. Symmetric Encryption
Symmetric encryption algorithms are the earliest encryption algorithms, also known as shared key encryption algorithms. In symmetric encryption algorithms, there is only one key used, and both the sender and receiver use this key to encrypt and decrypt data. This requires that both the encryption and decryption parties must know the encryption key in advance.

1. Data encryption process: In symmetric encryption algorithms, the data sender processes the plaintext (original data) together with the encryption key through special encryption processing, generating complex encrypted ciphertext for sending.
2. Data decryption process: After the data receiver receives the ciphertext, if they want to read the original data, they need to use the key used for encryption and the same algorithm’s inverse algorithm to decrypt the ciphertext, so that it can be restored to readable plaintext.
3.2. Asymmetric Encryption
Asymmetric encryption algorithms, also known as public key encryption algorithms, require two keys: one is called the public key (public key
), and the other is called the private key (private key
).
Since encryption and decryption use two different keys, this algorithm is called asymmetric encryption algorithm.

-
If data is encrypted using the public key, it can only be decrypted with the corresponding private key.
-
If data is encrypted using the private key, it can only be decrypted with the corresponding public key.
Example: Party A generates a key pair and makes one of them a public key available to others. Party B, upon obtaining this public key, uses it to encrypt confidential information and then sends it to Party A, who then uses their other private key to decrypt the encrypted information.
4. Common Signature Encryption Algorithms
4.1. MD5 Algorithm
MD5
uses a hash function, and its typical application is to generate an information digest for a piece of information to prevent tampering. Strictly speaking, MD5
is not an encryption algorithm but a digest algorithm. Regardless of the length of the input, MD5
will output a string of length 128bits
(usually represented as 32
hexadecimal characters).
public static final byte[] computeMD5(byte[] content) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
return md5.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
4.2. SHA1 Algorithm
SHA1
is a popular message digest algorithm like MD5
, but SHA1
is considered more secure than MD5
. For messages shorter than 2 ^ 64
bits, SHA1
produces a 160
bit message digest. Based on the digest characteristics of MD5
and SHA1
, as well as their irreversibility (generally speaking), they can be applied in scenarios like checking file integrity and digital signatures.
public static byte[] computeSHA1(byte[] content) {
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
return sha1.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
4.3. HMAC Algorithm
HMAC
is a key-related hash-based message authentication code, which uses hash algorithms (like MD5
, SHA1
, etc.) as input along with a key and a message to generate a message digest as output.
HMAC
is computed using a key that both the sender and receiver possess, and a third party without this key
cannot compute the correct hash value, thus preventing data tampering.
package net.pocrd.util;
import net.pocrd.annotation.NotThreadSafe;
import net.pocrd.define.ConstField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
@NotThreadSafe
public class HMacHelper {
private static final Logger logger = LoggerFactory.getLogger(HMacHelper.class);
private Mac mac;
/**
* MAC algorithms can be one of the following:
* HmacMD5/HmacSHA1/HmacSHA256/HmacSHA384/HmacSHA512
*/
private static final String KEY_MAC = "HmacMD5";
public HMacHelper(String key) {
try {
SecretKey secretKey = new SecretKeySpec(key.getBytes(ConstField.UTF8), KEY_MAC);
mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
} catch (Exception e) {
logger.error("create hmac helper failed.", e);
}
}
public byte[] sign(byte[] content) {
return mac.doFinal(content);
}
public boolean verify(byte[] signature, byte[] content) {
try {
byte[] result = mac.doFinal(content);
return Arrays.equals(signature, result);
} catch (Exception e) {
logger.error("verify sig failed.", e);
}
return false;
}
}
Test Conclusion: The
HMAC
algorithm instance is not safe in a multithreaded environment. However, when multithreaded access is required, a synchronization helper class usingThreadLocal
to cache an instance for each thread can avoid the need for lock operations.
4.4. AES/DES/3DES Algorithms
AES
, DES
, and 3DES
are all symmetric block encryption algorithms, and the encryption and decryption processes are reversible. Commonly used variants include AES128
, AES192
, and AES256
(the default installed JDK
does not support AES256
, requiring the installation of the corresponding jce
patch for upgrades to jce1.7
or jce1.8
).
4.4.1. DES Algorithm
The DES
encryption algorithm is a type of block cipher that encrypts data in 64
bit blocks, with a key length of 56
bits, using the same algorithm for both encryption and decryption.
The DES
encryption algorithm keeps the key confidential while the algorithm itself is public, including both encryption and decryption algorithms. Thus, only those who possess the same key as the sender can decipher the ciphertext encrypted by the DES
algorithm. Therefore, breaking the DES
encryption algorithm essentially means searching for the key encoding. For a 56
bit key, if using a brute force method to search, the number of operations required is 2 ^ 56
.
4.4.2. 3DES Algorithm
This is a symmetric algorithm based on DES
, which encrypts a block of data using three different keys for three times, providing higher strength.
4.4.3. AES Algorithm
The AES
encryption algorithm is the Advanced Encryption Standard in cryptography, employing a symmetric block cipher system with a minimum supported key length of 128
bits, 192
bits, or 256
bits, and a block length of 128
bits, designed to be easily implemented in various hardware and software. This encryption algorithm is the block encryption standard adopted by the U.S. federal government.
AES
was specifically designed to replace DES
, offering better security, efficiency, and flexibility.
import net.pocrd.annotation.NotThreadSafe;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
@NotThreadSafe
public class AesHelper {
private SecretKeySpec keySpec;
private IvParameterSpec iv;
public AesHelper(byte[] aesKey, byte[] iv) {
if (aesKey == null || aesKey.length < 16 || (iv != null && iv.length < 16)) {
throw new RuntimeException("Invalid initial key");
}
if (iv == null) {
iv = Md5Util.compute(aesKey);
}
keySpec = new SecretKeySpec(aesKey, "AES");
this.iv = new IvParameterSpec(iv);
}
public AesHelper(byte[] aesKey) {
if (aesKey == null || aesKey.length < 16) {
throw new RuntimeException("Invalid initial key");
}
keySpec = new SecretKeySpec(aesKey, "AES");
this.iv = new IvParameterSpec(Md5Util.compute(aesKey));
}
public byte[] encrypt(byte[] data) {
byte[] result = null;
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
result = cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
public byte[] decrypt(byte[] secret) {
byte[] result = null;
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
result = cipher.doFinal(secret);
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
public static byte[] randomKey(int size) {
byte[] result = null;
try {
KeyGenerator gen = KeyGenerator.getInstance("AES");
gen.init(size, new SecureRandom());
result = gen.generateKey().getEncoded();
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
}
4.5. RSA Algorithm
The RSA
encryption algorithm is currently the most influential public key encryption algorithm and is widely regarded as one of the best public key schemes. RSA
is the first algorithm that can be used for both encryption and digital signatures, and it can resist all known cryptographic attacks to date. It has been recommended by ISO
as a standard for public key data encryption.
The
RSA
encryption algorithm is based on a very simple number theory fact: multiplying two large primes is easy, but factoring their product is extremely difficult, allowing the product to be made public as the encryption key.
import net.pocrd.annotation.NotThreadSafe;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
@NotThreadSafe
public class RsaHelper {
private static final Logger logger = LoggerFactory.getLogger(RsaHelper.class);
private RSAPublicKey publicKey;
private RSAPrivateCrtKey privateKey;
static {
Security.addProvider(new BouncyCastleProvider()); // Use bouncycastle as the encryption algorithm implementation
}
public RsaHelper(String publicKey, String privateKey) {
this(Base64Util.decode(publicKey), Base64Util.decode(privateKey));
}
public RsaHelper(byte[] publicKey, byte[] privateKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
if (publicKey != null && publicKey.length > 0) {
this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
}
if (privateKey != null && privateKey.length > 0) {
this.privateKey = (RSAPrivateCrtKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RsaHelper(String publicKey) {
this(Base64Util.decode(publicKey));
}
public RsaHelper(byte[] publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
if (publicKey != null && publicKey.length > 0) {
this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public byte[] encrypt(byte[] content) {
if (publicKey == null) {
throw new RuntimeException("public key is null.");
}
if (content == null) {
return null;
}
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int size = publicKey.getModulus().bitLength() / 8 - 11;
ByteArrayOutputStream baos = new ByteArrayOutputStream((content.length + size - 1) / size * (size + 11));
int left = 0;
for (int i = 0; i < content.length; ) {
left = content.length - i;
if (left > size) {
cipher.update(content, i, size);
i += size;
} else {
cipher.update(content, i, left);
i += left;
}
baos.write(cipher.doFinal());
}
return baos.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public byte[] decrypt(byte[] secret) {
if (privateKey == null) {
throw new RuntimeException("private key is null.");
}
if (secret == null) {
return null;
}
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int size = privateKey.getModulus().bitLength() / 8;
ByteArrayOutputStream baos = new ByteArrayOutputStream((secret.length + size - 12) / (size - 11) * size);
int left = 0;
for (int i = 0; i < secret.length; ) {
left = secret.length - i;
if (left > size) {
cipher.update(secret, i, size);
i += size;
} else {
cipher.update(secret, i, left);
i += left;
}
baos.write(cipher.doFinal());
}
return baos.toByteArray();
} catch (Exception e) {
logger.error("rsa decrypt failed.", e);
}
return null;
}
public byte[] sign(byte[] content) {
if (privateKey == null) {
throw new RuntimeException("private key is null.");
}
if (content == null) {
return null;
}
try {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(privateKey);
signature.update(content);
return signature.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean verify(byte[] sign, byte[] content) {
if (publicKey == null) {
throw new RuntimeException("public key is null.");
}
if (sign == null || content == null) {
return false;
}
try {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initVerify(publicKey);
signature.update(content);
return signature.verify(sign);
} catch (Exception e) {
logger.error("rsa verify failed.", e);
}
return false;
}
}
4.6. ECC Algorithm
ECC
is also a type of asymmetric encryption algorithm, with the main advantage being that in certain cases, it uses smaller keys compared to other methods, such as RSA
encryption algorithms, providing an equivalent or higher level of security. However, a downside is that the encryption and decryption operations take longer to implement compared to other mechanisms (the algorithm consumes significant CPU
resources compared to RSA
).
import net.pocrd.annotation.NotThreadSafe;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
@NotThreadSafe
public class EccHelper {
private static final Logger logger = LoggerFactory.getLogger(EccHelper.class);
private static final int SIZE = 4096;
private BCECPublicKey publicKey;
private BCECPrivateKey privateKey;
static {
Security.addProvider(new BouncyCastleProvider());
}
public EccHelper(String publicKey, String privateKey) {
this(Base64Util.decode(publicKey), Base64Util.decode(privateKey));
}
public EccHelper(byte[] publicKey, byte[] privateKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
if (publicKey != null && publicKey.length > 0) {
this.publicKey = (BCECPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
}
if (privateKey != null && privateKey.length > 0) {
this.privateKey = (BCECPrivateKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
}
} catch (ClassCastException e) {
throw new RuntimeException("", e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public EccHelper(String publicKey) {
this(Base64Util.decode(publicKey));
}
public EccHelper(byte[] publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
if (publicKey != null && publicKey.length > 0) {
this.publicKey = (BCECPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public byte[] encrypt(byte[] content) {
if (publicKey == null) {
throw new RuntimeException("public key is null.");
}
try {
Cipher cipher = Cipher.getInstance("ECIES", "BC");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int size = SIZE;
ByteArrayOutputStream baos = new ByteArrayOutputStream((content.length + size - 1) / size * (size + 45));
int left = 0;
for (int i = 0; i < content.length; ) {
left = content.length - i;
if (left > size) {
cipher.update(content, i, size);
i += size;
} else {
cipher.update(content, i, left);
i += left;
}
baos.write(cipher.doFinal());
}
return baos.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public byte[] decrypt(byte[] secret) {
if (privateKey == null) {
throw new RuntimeException("private key is null.");
}
try {
Cipher cipher = Cipher.getInstance("ECIES", "BC");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int size = SIZE + 45;
ByteArrayOutputStream baos = new ByteArrayOutputStream((secret.length + size + 44) / (size + 45) * size);
int left = 0;
for (int i = 0; i < secret.length; ) {
left = secret.length - i;
if (left > size) {
cipher.update(secret, i, size);
i += size;
} else {
cipher.update(secret, i, left);
i += left;
}
baos.write(cipher.doFinal());
}
return baos.toByteArray();
} catch (Exception e) {
logger.error("ecc decrypt failed.", e);
}
return null;
}
public byte[] sign(byte[] content) {
if (privateKey == null) {
throw new RuntimeException("private key is null.");
}
try {
Signature signature = Signature.getInstance("SHA1withECDSA", "BC");
signature.initSign(privateKey);
signature.update(content);
return signature.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean verify(byte[] sign, byte[] content) {
if (publicKey == null) {
throw new RuntimeException("public key is null.");
}
try {
Signature signature = Signature.getInstance("SHA1withECDSA", "BC");
signature.initVerify(publicKey);
signature.update(content);
return signature.verify(sign);
} catch (Exception e) {
logger.error("ecc verify failed.", e);
}
return false;
}
}
5. Comparison of Various Encryption Algorithms
5.1. Comparison of Hash Algorithms
Name | Security | Speed |
---|---|---|
SHA-1 | High | Slow |
MD5 | Medium | Fast |
5.2. Comparison of Symmetric Encryption Algorithms
Name | Key Length | Running Speed | Security | Resource Consumption |
---|---|---|---|---|
DES | 56 bits | Fast | Low | Medium |
3DES | 112 or 168 bits | Slow | Medium | High |
AES | 128, 192, 256 bits | Fast | High | Low |
5.3. Comparison of Asymmetric Encryption Algorithms
Name | Maturity | Security | Computation Speed | Resource Consumption |
---|---|---|---|---|
RSA | High | High | Medium | Medium |
ECC | High | High | Slow | High |
5.4. Comparison of Symmetric and Asymmetric Algorithms
5.4.1. Symmetric Algorithms
-
Key Management: Relatively difficult, not suitable for the internet, generally used in internal systems
-
Security: Medium
-
Encryption Speed: Fast by several orders of magnitude (software encryption and decryption speed is at least 100 times faster, capable of encrypting and decrypting several
M
megabits of data per second), suitable for large-scale data encryption and decryption processing
5.4.2. Asymmetric Algorithms
-
Key Management: Keys are easy to manage
-
Security: High
-
Encryption Speed: Relatively slow, suitable for small amounts of data encryption or data signing
Conclusion
This article introduced digital signatures, encryption and decryption, symmetric and asymmetric encryption, and then detailed several encryption algorithms such as MD5
, SHA-1
, HMAC
, DES/AES
, RSA
, and ECC
along with code examples.
– EOF –
1. The Past and Present of Encryption Algorithms
2. Concepts Related to Encryption Algorithms in HTTPS
3. After confessing, the girl sent me a five-layer encrypted password
If you find this article helpful, please share it with more people
Recommended to follow ‘Algorithm Enthusiasts’ to enhance your programming skills
Likes and views are the greatest support ❤️