Guía básica de tecnología de cifrado y descifrado (algoritmo Base64, Hex, AES, SM4, RSA)

Descripción general de la criptografía

La tecnología de encriptación es la tecnología de codificación y decodificación de información. La codificación consiste en traducir la información legible original (también conocida como texto sin formato) en formato de código (también conocido como texto cifrado). El proceso inverso es la decodificación (descifrado). El punto clave de la tecnología de encriptación es el algoritmo de encriptación, que se puede dividir en tres categorías:

  • Cifrado simétrico, como AES, SM4 (secreto nacional)
    • Principio básico: divida el texto sin formato en N grupos, luego use la clave para cifrar cada grupo para formar sus respectivos textos cifrados y, finalmente, combine todos los textos cifrados agrupados para formar el texto cifrado final.
    • Ventajas: algoritmo abierto, pequeña cantidad de cálculo, velocidad de encriptación rápida, alta eficiencia de encriptación
    • Defecto: ambas partes usan la misma clave, no se puede garantizar la seguridad
  • Cifrado asimétrico, como RSA, claves públicas y privadas ssh de git
    • Principio básico: se generan dos claves al mismo tiempo: clave privada y clave pública. La clave privada se mantiene en secreto y la clave pública se puede enviar a clientes confiables. La clave privada se usa para el cifrado y la clave pública se usa para el descifrado.
      • Cifrado de clave privada, solo con la clave privada o la clave pública se puede descifrar
      • Cifrado de clave pública, solo se puede descifrar la clave privada
    • Pros: seguro, difícil de descifrar
    • Desventajas: el algoritmo requiere mucho tiempo
    • Los algoritmos asimétricos se utilizan generalmente para transmitir las claves de los algoritmos de cifrado simétricos.
  • Cifrado irreversible como MD5, SHA
    • Principio básico: no es necesario utilizar una clave en el proceso de cifrado. Después de ingresar el texto sin formato, el sistema lo procesará directamente en texto cifrado a través de un algoritmo de cifrado. Estos datos cifrados no se pueden descifrar y el texto sin formato no se puede calcular a partir del texto cifrado.

codificador

Base64

Análisis de principio: URL de salto

Base64 es uno de los métodos de codificación más comunes para transmitir códigos de bytes de 8 bits en Internet Base64 es un método para representar datos binarios basado en 64 caracteres imprimibles.

Base64 generalmente se usa para transmitir datos binarios bajo el protocolo HTTP.Dado que el protocolo HTTP es un protocolo de texto, es necesario convertir datos binarios en datos de caracteres para transmitir datos binarios bajo el protocolo HTTP. Sin embargo, una conversión directa no es posible. Porque la transmisión de red solo puede transmitir caracteres imprimibles.

**Caracteres imprimibles:**Según el código ASCII, 33 caracteres del 0 al 31 y 127 son caracteres de control, y 95 caracteres del 32 al 126 son caracteres imprimibles, es decir, la transmisión de red solo puede transmitir estos 95 caracteres, y los caracteres fuera de este rango no se pueden transmitir. Entonces, ¿cómo se pueden transmitir otros caracteres? Una forma es usar Base64.

La mayoría de las codificaciones son el proceso de convertir cadenas en binarias, mientras que la codificación Base64 convierte binarias en cadenas. Todo lo contrario de la norma.

La codificación Base64 se utiliza principalmente en el campo de la transmisión, el almacenamiento y la representación binaria. No se puede considerar como cifrado, pero el texto sin formato no se puede ver directamente. El cifrado también se puede realizar mezclando la codificación Base64.

Existen múltiples codificaciones para chino (p. ej., utf-8, gb2312, gbk, etc.), y diferentes codificaciones corresponden a diferentes resultados de codificación Base64.


La clase de utilidad java.util.Base64: esta clase consta solo de métodos estáticos para codificadores y decodificadores para obtener el esquema de codificación Base64.

efecto:

  • Use el codificador dentro de Base64 para codificar (cifrar) los datos
  • Use el decodificador en Base64 para decodificar (descifrar) los datos

Método estático de la clase de herramienta Base64:

// 使用Basic型base64编码方案
static Base64.Encoder getEncoder() 		// 获取加密器(编码器)
static Base64.Decoder getDecoder() 		// 获取解密器(解码器)
    
// 使用MIME型base64编码方案
static Base64.Encoder getMineEncoder() 		// 获取加密器(编码器)
static Base64.Decoder getMineDecoder() 		// 获取解密器(解码器)
static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)	// 指定行长度和行分隔符获取加密器
    
// 使用 URL and Filename safe 型base64编码方案
static Base64.Encoder getUrlEncoder() 		// 获取加密器(编码器)
static Base64.Decoder getUrlDecoder() 		// 获取解密器(解码器)

java.util.Base64.Encoder: es la clase interna de Base64, utilizada para cifrar datos

Métodos miembros de Encoder:

String encodeToString(byte[] src) 		// 使用Base64编码方案将指定的字节数组编码为字符串。

java.util.Base64.Decoder: es la clase interna de Base64, utilizada para descifrar datos

Métodos miembros de Decoder:

 byte[] decode(String src) 		// 使用Base64编码方案将Base64编码的字符串解码为新分配的字节数组。

hexadecimal (hexadecimal)

Hexadecimal, nombre en inglés: El sistema numérico hexadecimal (abreviado como hex) es un sistema de conteo con una base de 16, y es un sistema base que suma 1 cada 16.

Por lo general, consta de 0 9, AF, entre los cuales: A ~ F representan 10 ~ 15, estos se denominan números hexadecimales.


Clase de herramienta personalizada: EncoderUtils

import java.util.Base64;

public class EncoderUtils {
    
    

    public static final String UTF_8 = "UTF-8";
    public static final String BASE_64 = "base64";
    public static final String HEX = "hex";

    /**
     * base64 编码
     */
    public static String encodeByteToBase64(byte[] byteArray) {
    
    
        return Base64.getEncoder().encodeToString(byteArray);
    }

    /**
     * base64 解码
     */
    public static byte[] decodeBase64ToByte(String base64EncodedString) {
    
    
        return Base64.getDecoder().decode(base64EncodedString);
    }

    /**
     * 将二进制转换成十六进制(Hex)
     */
    public static String parseByte2HexStr(byte[] buf){
    
    
        StringBuilder sb = new StringBuilder();
        for (byte b : buf) {
    
    
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) {
    
    
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * 将十六进制(Hex)转换成二进制
     */
    public static byte[]  parseHexStr2Byte(String hexStr){
    
    
        if (hexStr.length() < 1){
    
    
            return null;
        } else {
    
    
            int half = hexStr.length() / 2;
            byte[] result = new byte[half];
            for (int i = 0; i < half; i++) {
    
    
                int high = Integer.parseInt(hexStr.substring(i*2, i*2 + 1), 16);
                int low = Integer.parseInt(hexStr.substring(i*2 + 1, i*2 + 2), 16);
                result[i] = (byte) (high * 16 + low);
            }
            return result;
        }
    }
}

Cifrado simétrico

algoritmo AES

descripción general

El nombre completo del algoritmo de cifrado AES es Estándar de cifrado avanzado (Advanced Encryption Standard), que es uno de los algoritmos de cifrado simétrico más comunes.

El cifrado AES requiere: texto sin formato + clave + desplazamiento (IV) + modo de cifrado (algoritmo/modo/relleno)

El descifrado AES requiere: texto cifrado + clave + compensación (IV) + modo de cifrado (algoritmo/modo/relleno)

El modo de contraseña de AES es generalmente AES/CBC/PKCS5Padding

  • AES: algoritmo de cifrado y descifrado

  • CBC: modo de paquete de datos

  • PKCS5Padding: los datos se agrupan de acuerdo con un cierto tamaño, y el grupo restante finalmente se divide.Si la longitud no es suficiente, debe completarse.También se le puede llamar el modo de relleno.


Proceso de cifrado en el trabajo real

En el trabajo real, la interacción entre el cliente y el servidor generalmente tiene un formato de cadena, por lo que un mejor proceso de encriptación es:

  • Proceso de cifrado: el texto sin formato pasa a través de la clave (a veces también requiere una compensación), utiliza el algoritmo de cifrado AES y luego se transcodifica a través de Base64 y finalmente genera una cadena cifrada.
  • Proceso de descifrado: la cadena cifrada pasa la clave (a veces también se requiere compensación), utiliza el algoritmo de descifrado AES y luego se transcodifica a través de Base64 y finalmente genera la cadena descifrada.

modo de paquete de datos

Los modos de paquetes de datos AES son los siguientes:

  • Modo de libro de códigos electrónico (Libro de códigos electrónico, ECB): el mensaje que se va a cifrar se divide en varios bloques según el tamaño de bloque del cifrado de bloque, y cada bloque se cifra de forma independiente.

  • Modo calculadora (CTR)

  • Modo de retroalimentación de cifrado (CFB)

  • Modo de retroalimentación de salida (OFB)

  • Modo de encadenamiento de bloques de cifrado (CBC)

    Todo el texto sin formato se corta en varias piezas pequeñas, y cada pieza pequeña se somete a XOR con el bloque inicial o el texto cifrado de la pieza anterior, y luego se cifra con la clave.

    En el modo CBC, cuando se usa el método de cifrado y descifrado AES para el cifrado y descifrado de paquetes, se deben usar dos parámetros:

    • Vector de inicialización, que es el desplazamiento
    • Clave de cifrado y descifrado

    inserte la descripción de la imagen aquí


patrón de relleno

Al cifrar y descifrar datos, los datos generalmente se dividen en varios grupos de acuerdo con un tamaño fijo (tamaño de bloque), y luego surge un problema: si el último grupo no es suficiente para un tamaño de bloque, debe complementarse.

Por ejemplo, el libro de códigos electrónico (ECB) y el encadenamiento de bloques de texto cifrado (CBC) están diseñados para el cifrado de claves simétricas. El modo de trabajo de cifrado de bloques requiere que la longitud del texto sin formato de entrada sea un múltiplo entero de la longitud del bloque, por lo que la información debe completarse para cumplir con los requisitos.

Modos de llenado comunes:

Algoritmo/Modo/Relleno Longitud de datos cifrados de 16 bytes Longitud cifrada inferior a 16 bytes
AES/CBC/Sin relleno dieciséis no apoyo
Relleno AES/CBC/PKCS5 32 dieciséis
AES/CBC/ISO10126Relleno 32 dieciséis
AES/CFB/Sin relleno dieciséis Longitud de datos sin procesar
Relleno AES/CFB/PKCS5 32 dieciséis
AES/CFB/ISO10126Relleno 32 dieciséis
AES/ECB/Sin relleno dieciséis no apoyo
Relleno AES/ECB/PKCS5 32 dieciséis
AES/ECB/ISO10126Relleno 32 dieciséis
AES/OFB/Sin relleno dieciséis no apoyo
Relleno AES/OFB/PKCS5 32 dieciséis
AES/OFB/ISO10126Relleno 32 dieciséis
AES/PCBC/Sin relleno dieciséis no apoyo
Relleno AES/PCBC/PKCS5 32 dieciséis
Relleno AES/PCBC/ISO10126 32 dieciséis

Compensar

Generalmente, es para aumentar AESla complejidad del cifrado y aumentar la seguridad de los datos. Generalmente, el desplazamiento se usa en AES_256, pero no en el cifrado AES_128


conjunto de caracteres

En el cifrado AES, se debe prestar especial atención al problema de los juegos de caracteres. Los conjuntos de caracteres generalmente utilizados son utf-8 y gbk.


Introducción al proceso de cifrado específico de AES

inserte la descripción de la imagen aquí

  • Texto sin formato P: datos sin cifrar

  • Clave K: la contraseña utilizada para cifrar el texto sin formato.En el algoritmo de cifrado simétrico, las claves de cifrado y descifrado son las mismas

    La clave se genera a través de la negociación entre el receptor y el remitente, pero no se puede transmitir directamente en la red, de lo contrario, la clave se filtrará. Por lo general, la clave se cifra mediante un algoritmo de cifrado asimétrico y luego se transmite a la otra parte a través de la red, o la clave se discute directamente cara a cara. La clave nunca debe filtrarse, de lo contrario, el atacante restaurará el texto cifrado y robará datos confidenciales.

  • Función de cifrado AES: ingrese el texto sin formato P y la clave K como parámetros de la función de cifrado, luego la función de cifrado E generará el texto cifrado C

  • Texto cifrado C: datos procesados ​​por la función de cifrado

  • Función de descifrado AES: ingrese el texto cifrado C y la clave K como parámetros de la función de descifrado, y la función de descifrado generará el texto sin formato P


método de cifrado AES

AES es el cifrado de bloques, el cifrado de bloques consiste en dividir el texto sin formato en grupos, cada grupo tiene la misma longitud y cifrar un grupo de datos cada vez hasta que se cifra todo el texto sin formato.

AES Longitud de clave (palabra de 32 bits) Longitud del paquete (palabra de 32 bits) Número de rondas de cifrado
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

En la especificación estándar AES, la longitud del paquete solo puede ser de 128 bits, es decir, cada paquete tiene 16 bytes, cada byte tiene 8 bits y la longitud de la clave puede ser de 128 bits, 192 bits o 258 bits. La longitud de la clave es diferente y el número recomendado de rondas de cifrado también es diferente. Por ejemplo, AES-128 significa que la longitud de la clave es de 128 bits, y el número de rondas de cifrado es de 10 rondas, AES-192 es de 12 rondas y AES-256 es de 14 rondas.

Tomando AES-128 como ejemplo, hay cuatro operaciones en una ronda de cifrado: sustitución de bytes, desplazamiento de fila, mezcla de columnas, adición de clave de ronda

  • Sustitución de bytes: la sustitución de caracteres AES es en realidad una operación de búsqueda de tabla simple. AES define un cuadro S y un cuadro S inverso.

  • Cambio de fila: Es una operación simple de cambio circular a la izquierda.

  • Mezcla de columnas: Se realiza por multiplicación de matrices, y la matriz de estado desplazado se multiplica por la matriz fija para obtener la matriz de estado confuso.

  • Adición de clave redonda: es realizar una operación XOR bit a bit en la clave redonda Ki de 128 bits y los datos en la matriz de estado.


Clase de herramienta personalizada: AESUtils

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
 * AES-128-CBC加密模式
 */
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class AESUtils {
    
    

    public static final String AES = "AES";
    // 填充模式,"算法/模式/补码方式"
    public static final String PADDING = "AES/CBC/PKCS5Padding";

    // 密钥,长度16位,可以用26个字母和数字组成
    private String key = "hj7x89H$yuBI0456";
    // 偏移量,长度16位。增加加密算法的强度
    private String iv = "NIfb&95GUY86Gfgh";


    /**
     * @Description AES算法加密明文
     * @param data 明文
     * @return 密文字节数组
     */
    public byte[] encryptAES(String data) throws Exception {
    
    
        // "算法/模式/补码方式"
        Cipher cipher = Cipher.getInstance(PADDING);
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
        // CBC模式,需要一个向量iv,可增加加密算法的强度
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        return cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }

    public String encryptAESAndEncoder(String data, String strOutType) throws Exception {
    
    
        byte[] encrypted = encryptAES(data);
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            // BASE64做转码。同时能起到2次加密的作用。
            return EncoderUtils.encodeByteToBase64(encrypted).trim();
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        }
    }

    public String encryptAESAndHexEncoder(String data) throws Exception {
    
    
        return encryptAESAndEncoder(data, EncoderUtils.HEX);
    }

    /**
     * @Description AES算法解密密文
     * @param data 密文
     * @return 明文
     */
    public String decryptAES(byte[] data) throws Exception {
    
    
        // "算法/模式/补码方式"
        Cipher cipher = Cipher.getInstance(PADDING);
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(data));
    }

    public String decryptAESAndDecode(String data, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(data);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        }
        return decryptAES(decodeData);
    }

    public String decryptAESAndHexDecode(String data) throws Exception {
    
    
        return decryptAESAndDecode(data, EncoderUtils.HEX);
    }

    public boolean verifyAES(String cipherText, String paramStr, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(cipherText);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        }
        return paramStr.equalsIgnoreCase(decryptAES(decodeData));
    }

    public boolean verifyAES(String cipherText, String paramStr) throws Exception {
    
    
        return verifyAES(cipherText, paramStr, EncoderUtils.HEX);
    }

    public static void main(String[] args) throws Exception {
    
    
        // 待加密的字串
        String data = "password123";
        AESUtils aesUtils = new AESUtils();
        String encryptedData = aesUtils.encryptAESAndHexEncoder(data);
        System.out.println("加密结果:" + encryptedData);
        String decryptedData = aesUtils.decryptAESAndHexDecode(encryptedData);
        System.out.println("解密结果:" + decryptedData);
    }
}

Algoritmo SM4

descripción general

El algoritmo de cifrado de bloques SM4 es un algoritmo de cifrado de bloques utilizado en los estándares inalámbricos de China. En 2012, la Administración Estatal de Criptografía Comercial lo identificó como el estándar nacional de la industria de cifrado. El número estándar era GM/T 0002-2012 y se renombró como algoritmo SM4. Junto con el algoritmo de cifrado de clave pública de curva elíptica SM2 y el algoritmo hash de cifrado SM3, sirve como estándar nacional de la industria de cifrado y desempeña un papel extremadamente importante en la industria de cifrado de mi país.

La longitud de la clave y la longitud del bloque del algoritmo SM4 son de 128 bits, y los algoritmos de cifrado y descifrado utilizan una estructura iterativa Feistel desequilibrada de 32 rondas, que apareció por primera vez en el algoritmo de expansión de clave del cifrado de bloque LOKI. SM4 agrega una transformación de secuencia inversa después de 32 rondas de iteraciones no lineales, de modo que solo se requiere que la clave de descifrado sea la secuencia inversa de la clave de cifrado, de modo que el algoritmo de descifrado pueda ser coherente con el algoritmo de cifrado. Por lo tanto, el algoritmo utilizado en el proceso de cifrado y descifrado del algoritmo SM4 es exactamente el mismo, la única diferencia es que la clave de descifrado de este algoritmo se obtiene mediante la transformación inversa de su clave de cifrado.

  • Operación básica: El algoritmo criptográfico SM4 utiliza la adición de módulo 2 y el desplazamiento cíclico como operación básica.
  • Componentes criptográficos básicos: el algoritmo criptográfico SM4 utiliza cajas S, transformación no lineal τ, componentes de transformación lineal L y transformación sintética T componentes criptográficos básicos.
  • Función de ronda: el algoritmo criptográfico SM4 adopta una estructura que itera la función de ronda básica. La función de ronda se puede formar utilizando los componentes criptográficos básicos mencionados anteriormente. La función de ronda del algoritmo criptográfico SM4 es una función criptográfica con la palabra como unidad de procesamiento.
  • Algoritmo de cifrado: el algoritmo de cifrado SM4 es un algoritmo de bloque. La longitud del paquete de datos es de 128 bits y la longitud de la clave es de 128 bits. El algoritmo de cifrado adopta una estructura iterativa de 32 rondas y cada ronda utiliza una clave de ronda.
  • Algoritmo de descifrado: el algoritmo de cifrado SM4 es una operación de involución, por lo que la estructura del algoritmo de descifrado es la misma que la del algoritmo de cifrado, excepto que se invierte el orden en que se usan las claves de ronda, y la clave de ronda de descifrado es el orden inverso de la clave de ronda de cifrado.
  • Algoritmo de expansión de clave: el algoritmo de encriptación SM4 utiliza una clave de encriptación de 128 bits y adopta una estructura de encriptación de 32 rondas. Cada ronda de encriptación usa una clave de ronda de 32 bits y se usa un total de 32 claves de ronda. Por lo tanto, es necesario utilizar el algoritmo de expansión de claves para generar 32 claves redondas a partir de la clave de cifrado.
  • Seguridad de SM4: el algoritmo criptográfico SM4 ha sido completamente analizado y probado por instituciones criptográficas profesionales en mi país, y puede resistir los ataques existentes, como ataques diferenciales y ataques lineales, por lo que es seguro.

Nota: El S-box es un componente de un cifrado de bloques construido mediante transformaciones no lineales. Está diseñado principalmente para realizar las características y el diseño de confusión en el proceso de cifrado de bloques. El S-box en el algoritmo SM4 está completamente diseñado de acuerdo con los estándares de diseño de los cifrados de bloque europeos y estadounidenses al principio, y el método que utiliza es un método compuesto de mapeo inverso de función afín que puede resistir ataques de diferencia.


Clase de herramienta personalizada: Sm4Utils

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Base64.Decoder;

@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class Sm4Utils {
    
    

    static {
    
    
        // 注册 BouncyCastle
        Security.addProvider(new BouncyCastleProvider());
    }

    public static final String SM4 = "SM4";
    // 加密算法/分组加密模式/分组填充方式
    // PKCS5Padding-以8个字节为一组进行分组加密
    // 定义分组加密模式使用:PKCS5Padding
    public static final String PADDING = "SM4/ECB/PKCS5Padding";
    // 128-32位16进制;256-64位16进制
    public static final int DEFAULT_KEY_SIZE = 128;

    // 密钥,长度16位,可以用26个字母和数字组成
    private String key = "hj7x89H$yuBI0456";

    private static Cipher generateEcbCipher(int mode, byte[] key) throws Exception {
    
    
        Cipher cipher = Cipher.getInstance(Sm4Utils.PADDING, BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(mode, new SecretKeySpec(key, SM4));
        return cipher;
    }

    public byte[] encryptSM4(byte[] data) throws Exception {
    
    
        Cipher cipher = generateEcbCipher(Cipher.ENCRYPT_MODE, key.getBytes(StandardCharsets.UTF_8));
        return cipher.doFinal(data);
    }

    public String encryptSM4AndEncoder(String data, String strOutType) throws Exception {
    
    
        byte[] encrypted = encryptSM4(data.getBytes(StandardCharsets.UTF_8));
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            // BASE64做转码。同时能起到2次加密的作用。
            return EncoderUtils.encodeByteToBase64(encrypted).trim();
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        }
    }

    public String encryptSM4AndHexEncoder(String data) throws Exception {
    
    
        return encryptSM4AndEncoder(data, EncoderUtils.HEX);
    }

    public byte[] decryptSM4(byte[] cipherText) throws Exception {
    
    
        Cipher cipher = generateEcbCipher(Cipher.DECRYPT_MODE, key.getBytes(StandardCharsets.UTF_8));
        return cipher.doFinal(cipherText);
    }

    public String decryptSM4AndDecode(String data, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(data);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        }
        return new String(decryptSM4(decodeData));
    }

    public String decryptSM4AndHexDecode(String data) throws Exception {
    
    
        return decryptSM4AndDecode(data, EncoderUtils.HEX);
    }

    public boolean verifySM4(String cipherText, String paramStr, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(cipherText);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        }
        return Arrays.equals(decryptSM4(decodeData), paramStr.getBytes(StandardCharsets.UTF_8));
    }

    public boolean verifySM4(String cipherText, String paramStr) throws Exception {
    
    
        return verifySM4(cipherText, paramStr, EncoderUtils.HEX);
    }

    public static void main(String[] args) throws Exception {
    
    
        // 待加密的字串
        String data = "张三";
        Sm4Utils sm4Utils = new Sm4Utils();
        String encryptedData = sm4Utils.encryptSM4AndHexEncoder(data);
        System.out.println("加密结果:" + encryptedData);
        String decryptedData = sm4Utils.decryptSM4AndHexDecode(encryptedData);
        System.out.println("解密结果:" + decryptedData);
    }
}

cifrado asimétrico

algoritmo RSA

descripción general

  • RSA se basa en el problema de factorización de números grandes. En la actualidad, todos los principales lenguajes informáticos admiten la implementación del algoritmo RSA.
  • Java6 y las versiones superiores admiten el algoritmo RSA
  • El algoritmo RSA se puede utilizar para el cifrado de datos y la firma digital
  • El algoritmo RSA es mucho más lento que los algoritmos de cifrado simétrico como DES/AES
  • Principios generales: cifrado de clave pública, descifrado de clave privada/cifrado de clave privada, descifrado de clave pública

Cómo utilizar

El algoritmo RSA es simple para construir un par de claves, aquí, los datos enviados por ambas partes se utilizan como modelo.

  1. La parte A crea un par de claves (clave pública + clave privada) localmente y publica la clave pública en la parte B

  2. La parte A cifra los datos con una clave privada y los envía a la parte B

  3. La Parte B descifra los datos con la clave pública proporcionada por la Parte A

Si la Parte B envía datos a la Parte A:

  1. La Parte B cifra los datos con la clave pública y luego los envía a la Parte A

  2. La parte A descifra los datos con la clave privada


Clase de herramienta personalizada: RSAUtils

import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * 非对称加密算法RSA算法组件
 */
public class RSAUtils {
    
    
    //非对称密钥算法
    public static final String KEY_ALGORITHM = "RSA";


    /**
     * 密钥长度,DH算法的默认密钥长度是1024
     * 密钥长度必须是64的倍数,在512到65536位之间
     */
    private static final int KEY_SIZE = 512;
    //公钥
    private static final String PUBLIC_KEY = "RSAPublicKey";

    //私钥
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 初始化密钥对
     *
     * @return Map 甲方密钥的Map
     */
    public static Map<String, Object> initKey() throws Exception {
    
    
        //实例化密钥生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        //初始化密钥生成器
        keyPairGenerator.initialize(KEY_SIZE);
        //生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //甲方公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //甲方私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        //将密钥存储在map中
        Map<String, Object> keyMap = new HashMap<String, Object>();
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;

    }


    /**
     * 私钥加密
     *
     * @param data 待加密数据
     * @param key       密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {
    
    

        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥加密
     *
     * @param data 待加密数据
     * @param key       密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
    
    

        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        //产生公钥
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);

        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
    
    
        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {
    
    

        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        //产生公钥
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, pubKey);
        return cipher.doFinal(data);
    }

    /**
     * 取得私钥
     *
     * @param keyMap 密钥map
     * @return byte[] 私钥
     */
    public static byte[] getPrivateKey(Map<String, Object> keyMap) {
    
    
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return key.getEncoded();
    }

    /**
     * 取得公钥
     *
     * @param keyMap 密钥map
     * @return byte[] 公钥
     */
    public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception {
    
    
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return key.getEncoded();
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws Exception {
    
    
        //初始化密钥
        //生成密钥对
        Map<String, Object> keyMap = RSACoder.initKey();
        //公钥
        byte[] publicKey = RSACoder.getPublicKey(keyMap);
        //私钥
        byte[] privateKey = RSACoder.getPrivateKey(keyMap);
        System.out.println("公钥:" + Base64.encodeBase64String(publicKey));
        System.out.println("私钥:" + Base64.encodeBase64String(privateKey));

        System.out.println("==========密钥对构造完毕,甲方将公钥公布给乙方,开始进行加密数据的传输==========");
        String str = "RSA密码交换算法";
        System.out.println("===========甲方向乙方发送加密数据==============");
        System.out.println("原文:" + str);
        //甲方进行数据的加密
        byte[] code1 = RSACoder.encryptByPrivateKey(str.getBytes(), privateKey);
        System.out.println("加密后的数据:" + Base64.encodeBase64String(code1));
        System.out.println("===========乙方使用甲方提供的公钥对数据进行解密==============");
        //乙方进行数据的解密
        byte[] decode1 = RSACoder.decryptByPublicKey(code1, publicKey);
        System.out.println("乙方解密后的数据:" + new String(decode1));

        System.out.println("===========反向进行操作,乙方向甲方发送数据==============");

        str = "乙方向甲方发送数据RSA算法";
        System.out.println("原文:" + str);
        //乙方使用公钥对数据进行加密
        byte[] code2 = RSACoder.encryptByPublicKey(str.getBytes(), publicKey);
        System.out.println("===========乙方使用公钥对数据进行加密==============");
        System.out.println("加密后的数据:" + Base64.encodeBase64String(code2));

        System.out.println("=============乙方将数据传送给甲方======================");
        System.out.println("===========甲方使用私钥对数据进行解密==============");
        //甲方使用私钥对数据进行解密
        byte[] decode2 = RSACoder.decryptByPrivateKey(code2, privateKey);
        System.out.println("甲方解密后的数据:" + new String(decode2));
    }
}

expandir

Clase javax.crypto.Cipher

La clase javax.crypto.Cipher se ha introducido desde jdk1.4, y pertenece al paquete de expansión jdk Java Cryptographic Extension (JCE), que se utiliza principalmente para funciones de cifrado, descifrado y contraseña.

Algoritmos admitidos actualmente

En total, se admiten los siguientes algoritmos de cifrado: AES/DES/DESede/RSA

tipo de valor longitud de la contraseña ilustrar
AES/CBC/Sin relleno 128 Implementación del modo CBC del algoritmo AES
Relleno AES/CBC/PKCS5 128 Implementación del modo CBC del algoritmo AES y lleno de reglas de relleno PKCS5
AES/ECB/Sin relleno 128 Implementación del modo ECB del algoritmo AES
Relleno AES/ECB/PKCS5 128 Implementación en modo ECB del algoritmo AES y relleno con reglas de relleno PKCS5
DES/CBC/Sin relleno 56
DES/CBC/PKCS5Relleno 56
DESede/CBC/Sin Relleno 168
DESede/CBC/PKCS5Relleno 168
DESede/ECB/NoPadding 168
DESede/ECB/PKCS5Relleno 168
RSA/ECB/PKCS1Relleno (1024, 2048) La longitud de la contraseña es opcional.
Relleno RSA/ECB/OAEP con SHA-1 y MGF1 (1024, 2048) La longitud de la contraseña es opcional.
RSA/ECB/OAEPConSHA-256YMGF1Relleno (1024, 2048) La longitud de la contraseña es opcional.

Los modos de cifrado comunes son los siguientes:

  • ECB (Libro de libros de códigos electrónicos, modo de libro de códigos electrónicos): divida el texto sin formato en varios segmentos pequeños y luego cifre cada segmento pequeño
  • CBC (Encadenamiento de bloques de cifrado, modo de encadenamiento de bloques de cifrado): Primero, el texto sin formato se divide en varios segmentos pequeños, y luego cada segmento pequeño se XOR con el bloque inicial o el segmento de texto cifrado del segmento anterior, y luego se encripta con la clave

Las reglas comunes de llenado son las siguientes:

  • En la mayoría de los casos, el texto sin formato no es exactamente un múltiplo de N bits. Para el último paquete, si la longitud es inferior a N bits, debe llenarse con datos hasta N bits.

Cipher 类里面常用方法:

// 获取 Cipher 实现指定转换的对象
public static final Cipher getInstance(String transformation, String provider)
    
// 使用密钥和一组算法参数初始化此密码
public final void init(int opmod, Key key);
public final void init(int opmod, Certificate certificate);
public final void init(int opmod, Key key, SecureRandom random);
public final void init(int opmod, Certificate certificate, SecureRandom random);
public final void init(int opmod, Key key, AlgorithmParameterSpec params);
public final void init(int opmod, Key key, AlgorithmParameterSpec params, SecureRandom random);
public final void init(int opmod, Key key, AlgorithmParameters params);
public final void init(int opmod, Key key, AlgorithmParameters params,SecureRandom random);
    
// 完成多部分加密或解密操作,具体取决于此密码的初始化方式
public final byte[] doFinal(byte[] input);
public final byte[] doFinal(byte[] input, int inputOffset, int inputLen);
public final int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output);
public final int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,int outputOffset);

// 在多步加密或解密数据时,首先需要一次或多次调用update方法,用以提供加密或解密的所有数据
public byte[] update(byte[] input);
public byte[] update(byte[] input, int inputOffset, int inputLen);
public int update(byte[]input, int inputOffset, int inputLen, byte[] output);
public int update(byte[]input, int inputOffset, int inputLen, byte[] output, int outputOffset);
// 如果还有输入数据,多步操作可以使用前面提到的doFinal方法之一结束。如果没有数据,多步操作可以使用下面的doFinal方法之一结束
public final byte[] doFinal();
public final int doFinal(byte[] output, int outputOffset);

其中:

  • 通过调用 Cipher 类中的 getInstance() 静态工厂方法得到 Cipher 对象。

    参数 transformation :密码学算法名称,比如 DES,也可以在后面包含模式和填充方式

    两种写法:

    • “算法/模式/填充”,示例:“DES/CBC/PKCS5Padding”
    • “算法”,示例:“DES”

    如果没有指定模式或填充方式,就使用特定提供者指定的默认模式或默认填充方式。

    例如,SunJCE 提供者使用 ECB 作为 DES、DES-EDE 和 Blowfish 等 Cipher 的默认模式,并使用 PKCS5Padding 作为它们默认的填充方案。这意味着在 SunJCE 提供者中,下列形式的声明是等价的:

    • Cipherc1=Cipher.getInstance(“DES/ECB/PKCS5Padding”);
    • Cipher c1=Cipher.getInstance(“DES”);
  • getInstance() 工厂方法返回的对象没有进行初始化,因此在使用前必须进行初始化。

    通过 getInstance() 得到的 Cipher 对象必须使用下列四个模式之一进行初始化,这四个模式在 Cipher 类中被定义为 finalinteger常数,可以使用符号名来引用这些模式:

    • ENCRYPT_MODE :加密数据
    • DECRYPT_MODE :解密数据
    • WRAP_MODE :将一个 Key 封装成字节,可以用来进行安全传输
    • UNWRAP_MODE :将前述已封装的密钥解开成 java.security.Key 对象

    每个 Cipher 初始化方法使用一个模式参数 opmod,并用此模式初始化 Cipher 对象。此外还有其他参数,包括密钥 key、包含密钥的证书 certificate、算法参数 params 和随机源 random。

    必须指出的是,加密和解密必须使用相同的参数。当 Cipher 对象被初始化时,它将失去以前得到的所有状态。

  • 如果在 transformation 参数部分指定了 padding 或 unpadding 方式,则所有的 doFinal 方法都要注意所用的 padding 或unpadding 方式。

    调用 doFinal 方法将会重置 Cipher 对象到使用 init 进行初始化时的状态,就是说,Cipher 对象被重置,使得可以进行更多数据的加密或解密,至于这两种模式,可以在调用 init 时进行指定。

  • 包裹 wrap 密钥必须先使用 WRAP_MODE 初始化 Cipher 对象,然后调用以下方法:

    public final byte[] wrap(Key key);
    

    如果将调用 wrap 方法的结果(wrap 后的密钥字节)提供给解包裹 unwrap 的人使用,必须给接收者发送以下额外信息:

    1. 密钥算法名称

      密钥算法名称可以调用 Key 接口提供的 getAlgorithm 方法得到:

      public String getAlgorithm();
      
    2. 被包裹密钥的类型(Cipher.SECRET_KEY,Cipher.PRIVATE_KEY,Cipher.PUBLIC_KEY)

    为了对调用 wrap 方法返回的字节进行解包,必须先使用 UNWRAP_MODE 模式初始化 Ciphe r对象,然后调用以下方法:

    public final Keyunwrap(byte[] wrappedKey,String wrappedKeyAlgorithm,int wrappedKeyType));
    

    其中:

    • 参数 wrappedKey 是调用wrap方法返回的字节

    • 参数 wrappedKeyAlgorithm 是用来包裹密钥的算法

    • 参数 wrappedKeyType 是被包裹密钥的类型,该类型必须是

      Cipher.SECRET_KEY, Cipher.PRIVATE_KEY, Cipher.PUBLIC_KEY 三者之一


Bouncy Castle 密码包

概述

BouncyCastle(轻量级密码术包)是一种用于 Java 平台的开放源码的轻量级密码术包;Bouncycstle 包含了大量的密码算法,其支持椭圆曲线密码算法,并提供 JCE 1.2.1的实现。

La biblioteca estándar de Java proporciona una serie de algoritmos hash de uso común . Pero si la biblioteca estándar de Java no proporciona un cierto algoritmo para usar, debe escribirlo usted mismo o usar directamente una biblioteca de terceros preparada. BouncyCastle es una biblioteca de terceros que proporciona muchos algoritmos hash y algoritmos de cifrado. Proporciona algunos algoritmos que no están en la biblioteca estándar de Java, por ejemplo, el algoritmo hash RipeMD160.

Debido a que Bouncy Castle está diseñado para ser liviano, puede ejecutarse en plataformas desde J2SE 1.4 a J2ME (incluido MIDP). Es el único paquete criptográfico completo que se ejecuta en MIDP.


usar

  • Introduzca la dependencia del paquete jar de bouncycastle en el archivo pom.xml y coloque el paquete jar proporcionado por BouncyCastle en el classpath. Este paquete jar es bcprov-jdk15on-xxx.jar, que se puede descargar desde el sitio web oficial .

    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.59</version>
    </dependency>
    
  • El paquete java.security de la biblioteca estándar de Java proporciona un mecanismo estándar que permite una integración perfecta por parte de proveedores externos. Para utilizar el algoritmo RipeMD160 proporcionado por BouncyCastle, primero debe registrar BouncyCastle:

    @Test
    public void case1(){
          
          
        static {
          
          
            // 注册BouncyCastle:
        	Security.addProvider(new BouncyCastleProvider());
        }
        
        // 按名称正常调用:
        try {
          
          
            md = MessageDigest.getInstance("RipeMD160");
            md.update("HelloWorld".getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
          
          
            e.printStackTrace();
        }
        byte[] result = md.digest();
        System.out.println(new BigInteger(1, result).toString(16));
    }
    

    Nota: El registro de BouncyCastle se logra a través de la siguiente declaración. El registro solo debe realizarse una vez al inicio, y todos los algoritmos hash y algoritmos de cifrado proporcionados por BouncyCastle se pueden usar posteriormente.

    Security.addProvider(new BouncyCastleProvider());
    

Supongo que te gusta

Origin blog.csdn.net/footless_bird/article/details/124029210
Recomendado
Clasificación