preface

Digital signature and information encryption are technologies that are often used in front and back end development. Application scenarios include user login, transaction, information communication, oauth, etc. Different application scenarios also need to use different signature encryption algorithms, or need to match different signature encryption algorithms to achieve business goals. This section briefly introduces several common signature encryption algorithms and their applications in some typical scenarios.

The body of the

1. Digital signature

A digital signature, simply put, is a way to verify one’s identity by providing identifiable digital information. A set of digital signatures typically defines two complementary operations, one for signing and one for verification. The sender holds a private key that can represent his identity (the private key cannot be disclosed), and the receiver holds a public key corresponding to the private key, which can be used to verify the identity of the sender when receiving the information of the sender.

Note: The encryption process in the figure is different from public key encryption, more about the stamp here. The most fundamental purpose of a signature is to be able to uniquely prove the identity of the sender and prevent man-in-betweenattacks and CSRF cross-domain identity forgery. Based on this, signature algorithms are used in authentication systems such as device authentication, user authentication, and third-party authentication (the implementations of each may differ).

2. Encryption and decryption

2.1. The encryption

The basic process of data encryption is to process the original plaintext files or data into unreadable code according to some algorithm, which is usually called “ciphertext”. Through this way, to achieve the protection of data is not unlegal person to steal, the purpose of reading.

2.2. The decryption

The inverse process of encryption is decryption, which is the process of transforming the encoded information into its original data.

3. Symmetric encryption and asymmetric encryption

The encryption algorithm is divided into symmetric encryption and asymmetric encryption. The encryption key of symmetric encryption algorithm is the same as that of decryption, and the encryption key of asymmetric encryption algorithm is different from that of decryption. In addition, there is a hash algorithm that does not need the key.

The common symmetric encryption algorithms include DES, 3DES, and AES. The common asymmetric algorithms include RSA and DSA. The common hash algorithms include SHA-1 and MD5.

3.1. Symmetric encryption

Symmetric encryption algorithm, also known as shared key encryption algorithm, is an early encryption algorithm. In symmetric encryption, only one key is used, and both sender and receiver use this key to encrypt and decrypt the data. This requires both the encryption and decryption parties to know the encryption key in advance.

  1. Data encryption: In the symmetric encryption algorithm, the sender encrypts the plaintext (original data) and the encryption key together to generate complex encrypted ciphertext.

  2. Data decryption: If the data receiver wants to read the original data after receiving the ciphertext, it needs to use the encryption key and the inverse algorithm of the same algorithm to decrypt the encrypted ciphertext to restore it to readable plaintext.

3.2. Asymmetric encryption

Asymmetric encryption algorithm, also known as public key encryption algorithm. It requires two keys. One is called the public key, or public key, and the other is called the private key.

Because encryption and decryption use two different keys, this algorithm is called asymmetric encryption.

  1. If the public key is used to encrypt the data, only the corresponding private key can be used to decrypt the data.

  2. If the private key is used to encrypt the data, only the corresponding public key can be used to decrypt the data.

Example: Party A generates a pair of keys and discloses one of them as a public key to others. The obtained party B uses the key to encrypt the confidential information and then sends it to Party A. Party A then uses the other private key (private key) stored by itself to decrypt the encrypted information.

4. Common signature encryption algorithms

4.1. The MD5 algorithm

MD5 uses a hash function, and its typical use is to generate a summary of a piece of information to prevent tampering. Technically, MD5 is not an encryption algorithm but a digest algorithm. Regardless of the length of the input, MD5 outputs a string of 128bits (typically 32 characters in hexadecimal).

public static final byte[] computeMD5(byte[] content) {
    try {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        return md5.digest(content);
    } catch (NoSuchAlgorithmException e) {
        throw newRuntimeException(e); }}Copy the code

4.2. SHA1 algorithm

SHA1 is as popular a message digest algorithm as MD5, but it is more secure than MD5. For messages less than 2 ^ 64 bits in length, SHA1 produces a 160-bit message digest. The MD5 and SHA1-based summarization feature and its irreversibility (in general) can be used to check file integrity and digital signature scenarios.

public static byte[] computeSHA1(byte[] content) {
    try {
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        return sha1.digest(content);
    } catch (NoSuchAlgorithmException e) {
        throw newRuntimeException(e); }}Copy the code

4.3. HMAC algorithm

HMAC is a Hash-based Message Authentication Code. HMAC uses a Hash algorithm (MD5, SHA1, etc.) to input a key and a Message. Generate a message digest as output.

The HMAC sender and receiver both have a key to calculate, and a third party without the key cannot calculate 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;

    / * * * * HmacMD5 / MAC algorithm can choose the following various algorithm 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; }}Copy the code

Test conclusion: HMAC algorithm instance is not safe in multithreaded environment. But for helper classes that need to synchronize when multiple threads are accessed, using ThreadLocal to cache one instance per thread can avoid locking.

4.4. AES, DES / 3 DES algorithm

AES, DES, and 3DES are symmetric block encryption algorithms, and the process of encryption and decryption is reversible. AES128, AES192, and AES256 are commonly used. (The default JDK does not support AES256. You need to install the corresponding JCE patch to upgrade JCE1.7 and JCE1.8.)

4.4.1. DES algorithm

DES encryption algorithm is a block cipher, to 64 bits as a group of data encryption, its key length is 56 bits, encryption and decryption with the same algorithm.

The DES encryption algorithm is to keep the key secret, while the public algorithm, including encryption and decryption algorithm. In this way, only those who have the same key as the sender can interpret the ciphertext data encrypted by the DES encryption algorithm. Therefore, decoding THE DES encryption algorithm is actually the search key encoding. For a 56-bit key, the number of searches performed by exhaustive method is 2 ^ 56.

4.4.2. 3 des algorithm

Is a symmetric algorithm based on DES, a piece of data with three different keys for three times encryption, the strength of higher.

4.4.3. AES algorithm

AES encryption algorithm is an advanced encryption standard in cryptography. The encryption algorithm uses symmetric block cipher system. The minimum key length is 128 bits, 192 bits, 256 bits, and the block length is 128 bits. This encryption algorithm is the block encryption standard adopted by the U.S. federal government.

AES is designed to replace DES. AES has 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("Wrong 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("Wrong 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);
        }
        returnresult; }}Copy the code

4.5. The RSA algorithm

RSA encryption algorithm is the most influential public key encryption algorithm at present, and is generally regarded as one of the best public key schemes. RSA is the first algorithm that can be used for both encryption and digital signature. It is able to resist all known cryptographic attacks to date and has been recommended by ISO as the Public key data encryption standard.

The RSA encryption algorithm is based on a very simple number theoretic fact: It is easy to multiply two large prime numbers, but extremely difficult to factor the product, so the product can 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
    }

    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(newPKCS8EncodedKeySpec(privateKey)); }}catch (Exception e) {
            throw newRuntimeException(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(newX509EncodedKeySpec(publicKey)); }}catch (Exception e) {
            throw newRuntimeException(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 newRuntimeException(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 newRuntimeException(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; }}Copy the code

4.6. The ECC algorithm

ECC is also an asymmetric encryption algorithm, with the main advantage that in some cases it uses a smaller key than other methods, such as the RSA encryption algorithm, providing an equivalent or higher level of security. One drawback, however, is that the encryption and decryption operations take longer to implement than the other mechanisms (which are CPU intensive 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(newPKCS8EncodedKeySpec(privateKey)); }}catch (ClassCastException e) {
            throw new RuntimeException("", e);
        } catch (Exception e) {
            throw newRuntimeException(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(newX509EncodedKeySpec(publicKey)); }}catch (Exception e) {
            throw newRuntimeException(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 newRuntimeException(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 newRuntimeException(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; }}Copy the code

5. Comparison of various encryption algorithms

5.1. Hash algorithm comparison

The name of the security speed
SHA-1 high slow
MD5 In the fast

5.2. Comparison of symmetric encryption algorithms

The name of the The key name The running speed security Resource consumption
DES 56 faster low In the
3DES 112 bits or 168 bits slow In the high
AES 128, 192, 256 bits fast high low

5.3. Comparison of asymmetric encryption algorithms

The name of the maturity security speed Resource consumption
RSA high high In the In the
ECC high high slow high

5.4. Symmetric algorithm and asymmetric encryption algorithm

5.4.1. Symmetry algorithm

  1. Key management: difficult, not suitable for the Internet, generally used for internal systems

  2. Security: medium

  3. Encryption speed: several orders of magnitude faster (software encryption and decryption speed at least 100 times faster, per second can encryption and decryption M bit data), suitable for large amount of data encryption and decryption processing

5.4.2. Asymmetric algorithms

  1. Key management: The key is easy to manage

  2. Security: high

  3. Encryption speed: relatively slow, suitable for small data encryption and decryption or data signature

summary

This article introduces digital signature, encryption and decryption, symmetric and asymmetric encryption, and then details MD5, SHA-1, HMAC, DES/AES, RSA, and ECC encryption algorithms and code examples.


Welcome to pay attention to the technical public number: one technology Stack

This account will continue to share backend technology essentials, including virtual machine fundamentals, multi-threaded programming, high-performance frameworks, asynchronous, cache and messaging middleware, distributed and microservices, architecture learning and advanced learning materials and articles.