An Overview of 7 Common Encryption Algorithms and Their Implementations

(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.

An Overview of 7 Common Encryption Algorithms and Their Implementations

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 include RSA, DSA, etc. Hash algorithms mainly include SHA-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.

An Overview of 7 Common Encryption Algorithms and Their Implementations

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.

An Overview of 7 Common Encryption Algorithms and Their Implementations
  1. If data is encrypted using the public key, it can only be decrypted with the corresponding private key.

  2. 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 using ThreadLocal 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

  1. Key Management: Relatively difficult, not suitable for the internet, generally used in internal systems

  2. Security: Medium

  3. 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

  1. Key Management: Keys are easy to manage

  2. Security: High

  3. 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 –

Recommended Reading Click the title to jump

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

An Overview of 7 Common Encryption Algorithms and Their Implementations

Likes and views are the greatest support ❤️

Leave a Comment