Ir al contenido principal

Buscar en CooLWJ

选择搜索范围,输入关键词后按回车。

Idioma

El arte de cifrar y descifrar

Uno Cifrado simétrico

El cifrado simétrico y el cifrado asimétrico son dos métodos de cifrado diferentes que desempeñan un papel importante en la seguridad de los datos y la transmisión de información. A continuación presentaré ambas tecnologías de cifrado por separado:

1.1 Cifrado simétrico

El cifrado simétrico consiste en utilizar la misma clave tanto para cifrar como para descifrar. Esto significa que tanto el emisor como el receptor deben disponer de esa clave para realizar las operaciones de cifrado y descifrado.

Algoritmos habituales de cifrado simétrico:

  • AES(Estándar de Cifrado Avanzado)

  • DES(Estándar de Cifrado de Datos)

  • 3DES(algoritmo de cifrado de datos triple)

1.2 Características del cifrado simétrico

En general, el cifrado simétrico presenta las siguientes características:

  1. Mayor velocidad:Como el cifrado y el descifrado utilizan la misma clave, el cifrado simétrico suele ser más rápido que el asimétrico.

  2. Gestión de claves:El principal desafío del cifrado simétrico reside en la distribución y gestión de las claves. Si se filtra la clave, la seguridad del cifrado se verá comprometida.

  3. Adecuado para grandes volúmenes de datos:Gracias a su rapidez, el cifrado simétrico es adecuado para cifrar grandes cantidades de datos.

De aquí se desprende que el cifrado simétrico tiene dos grandes ventajas: la primera es la rapidez de cálculo; la segunda, su idoneidad para grandes cantidades de datos.

Sin embargo, el cifrado simétrico tiene un problema fatal: la gestión de claves. ¡Cómo transmitir la clave de forma segura del servidor al cliente es todo un problema! Además, cuando la comunicación es de uno a muchos, gestionar las claves para evitar que se filtren también supone un desafío. Esta es una deficiencia del cifrado simétrico.

Ilustración artística de una pared verde azulada y un rodillo de pintura

1.3 Ejemplo de código

A continuación, yo, Song, os mostraré cómo realizar el cifrado y descifrado simétricos con código Java.

Para implementar el cifrado simétrico en Java, normalmente se utilizan las clases e interfaces que proporciona la arquitectura criptográfica de Java (Java Cryptography Architecture, JCA).

A continuación se muestra un ejemplo sencillo de cifrado y descifrado simétricos con el algoritmo AES(Estándar de Cifrado Avanzado):

public class SymmetricEncryptionExample {    // 生成密钥
    public static SecretKey generateKey() throws Exception {     
       KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(128); // 可以是128, 192或256位
        return keyGenerator.generateKey();
    }    
    // 加密方法
    public static String encrypt(String data, SecretKey key) throws Exception {      
      Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);       
         byte[] encryptedBytes = cipher.doFinal(data.getBytes());        
         return Base64.getEncoder().encodeToString(encryptedBytes);
    }    
    // 解密方法
    public static String decrypt(String encryptedData, SecretKey key) throws Exception {        
    Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);       
         byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));       
          return new String(decryptedBytes);
    }    
    public static void main(String[] args) {        
    try {           
             // 生成密钥
            SecretKey key = generateKey();           
             // 原始数据
            String originalData = "Hello, JavaBoy!";            
            // 加密
            String encryptedData = encrypt(originalData, key);
            System.out.println("加密数据: " + encryptedData);            
            // 解密
            String decryptedData = decrypt(encryptedData, key);
            System.out.println("解密数据: " + decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. Generar una clave:utilizar KeyGenerator Generar una clave AES.

  2. Cifrado:utilizar Cipher para cifrar mediante esta clase, convirtiendo los datos en bytes antes de cifrarlos y utilizando la codificación Base64 para convertirlos en una cadena, lo que facilita su almacenamiento o transmisión.

  3. Descifrado:Descodificar la cadena cifrada para recuperar los bytes y, después, descifrarlos con la misma clave.

En el código anterior, hay que prestar atención a lo siguiente:

  • La longitud de la clave(por ejemplo, 128 bits)debe elegirse según las necesidades de seguridad.

  • Asegúrate de almacenar las claves de forma segura y no las incluyas directamente en el código.

  • En entornos de producción, conviene considerar una estrategia de gestión de claves más segura.

Por motivos de seguridad, normalmente utilizamos el método anterior para generar la clave. Las claves generadas con este método tienen la particularidad de cambiar cada vez que se reinicia el sistema. Si deseas controlar tú mismo la generación de la clave, puedes hacerlo de la siguiente manera:

public static SecretKey generateKeyFromPassword(String password, int keySize) throws NoSuchAlgorithmException, InvalidKeySpecException {   
  SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");   
   KeySpec spec = new PBEKeySpec(password.toCharArray(), "salt".getBytes(), 65536, keySize);   
   return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}

Así se puede controlar la clave mediante los parámetros que uno mismo proporciona.

1.4 Combinación del frontend y el backend

Al utilizar el algoritmo AES para cifrar y descifrar entre distintos lenguajes(como JavaScript y Java), lo esencial es garantizar que ambos extremos utilicen la misma clave, el mismo modo del algoritmo(como CBC, ECB, etc.), el mismo esquema de relleno(como PKCS5Padding, PKCS7Padding, etc.)y el mismo vector de inicialización(IV, si se utiliza un modo que lo requiera, como CBC).

Antes, alguien comentó que, después de cifrar los datos en el frontend, nunca conseguía descifrarlos en el backend. Aquí, el hermano Song también ofrece un ejemplo que combina frontend y backend.

Cifrado en el frontend y descifrado en el backend

Código del frontend:

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>AES加密示例</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
    </head>
    <body>
    <script>
    function encryptAES(text, secretKey) {        
    const key = CryptoJS.enc.Utf8.parse(secretKey);        
    const iv = CryptoJS.lib.WordArray.random(128 / 8); 
    // 对于CBC模式,需要IV
    
        const encrypted = CryptoJS.AES.encrypt(text, key, {           
         iv: iv,           
          mode: CryptoJS.mode.CBC,            
          padding: CryptoJS.pad.Pkcs7
        });        
        // 返回加密后的文本和IV(Base64格式),实际使用中可能需要安全地传输这些值
        return {          
          ciphertext: encrypted.toString(),          
            iv: iv.toString(CryptoJS.enc.Base64)
        };
    }    
    const secretKey = 'helloworldhelloworldhelloworld11'; /
    / 确保密钥是32个字符长(256位)
    const text = 'Hello, javaboy!';    
    const result = encryptAES(text, secretKey);    
    console.log(result);
    </script>
    </body>
    </html>


Código del backend:

import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.util.Base64;public class AESDecrypt {    public static String decryptAES(String encryptedData, String secretKey, String iv) throws Exception {        IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES");        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);        byte[] decodedValue = Base64.getDecoder().decode(encryptedData);        byte[] decryptedValue = cipher.doFinal(decodedValue);        return new String(decryptedValue, "UTF-8");
    }    public static void main(String[] args) {        try {            //前端加密后的文本
            String encryptedText = "PYANpAjMsRnBIEhovtEXQw==";            String secretKey = "helloworldhelloworldhelloworld11";            //前端 IV,要和加密后的文本一起传到后端
            String iv = "y/jUHcgSOpOiyNlsfjNUBg==";            String decryptedText = decryptAES(encryptedText, secretKey, iv);
            System.out.println("解密文本: " + decryptedText);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Ten en cuenta que, al cifrar en el frontend, se genera un parámetro iv que debe enviarse al backend junto con el resultado del cifrado del frontend.

Cifrado en el backend y descifrado en el frontend

Al cifrar con AES en Java y descifrar en JavaScript, también hay que asegurarse de que ambos extremos utilicen la misma clave, el mismo modo del algoritmo(como CBC, ECB, etc.), el mismo esquema de relleno(como PKCS5Padding, PKCS7Padding, etc.)y, si corresponde, el mismo vector de inicialización(IV).

Código del backend:

public class AESEncrypt {    public static String encryptAES(String plainText, String secretKey, String iv) throws Exception {        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(256); // 对于AES-256
        SecretKey secret = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES");        IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret, ivParameterSpec);        byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));        return Base64.getEncoder().encodeToString(encrypted);
    }    public static void main(String[] args) {        try {            String plainText = "Hello, 江南一点雨!";            String secretKey = "helloworldhelloworldhelloworld11"; // 确保密钥是32个字符长(256位)
            String iv = Base64.getEncoder().encodeToString(new byte[16]); // 示例IV,实际应用中应更安全地生成

            String encryptedText = encryptAES(plainText, secretKey, iv);
            System.out.println("Encrypted text: " + encryptedText);
            System.out.println("IV (Base64): " + iv); // 确保将IV发送给解密方

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Código del frontend:

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
    <script>
        function decryptAES(ciphertext, secretKey, iv) {            const key = CryptoJS.enc.Utf8.parse(secretKey);            const ivParsed = CryptoJS.enc.Base64.parse(iv);            const decrypted = CryptoJS.AES.decrypt(
                {                    ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
                },
                key,
                {                    iv: ivParsed,                    mode: CryptoJS.mode.CBC,                    padding: CryptoJS.pad.Pkcs7
                }
            );            return decrypted.toString(CryptoJS.enc.Utf8);
        }        const secretKey = 'helloworldhelloworldhelloworld11';        const iv = 'AAAAAAAAAAAAAAAAAAAAAA=='; // 从Java代码获取
        const ciphertext = '1lPNVF1injas78KUWeKp5FusEr6f0pGgcrLAg9ELFr8='; // 从Java代码获取

        const decryptedText = decryptAES(ciphertext, secretKey, iv);        console.log(decryptedText);    </script></head><body></body></html>

Yo, Song Ge, he probado personalmente el código anterior de cifrado y descifrado para la comunicación entre frontend y backend, y funciona correctamente. Si necesitáis algo así, guardad este artículo para tenerlo como referencia.

II Cifrado asimétrico

2.1 Qué es el cifrado asimétrico

El cifrado asimétrico, también llamado cifrado de clave pública, es un método de cifrado que utiliza dos claves diferentes(una clave pública y una privada).

En general, el cifrado asimétrico presenta las siguientes características distintivas:

  1. Clave pública y clave privada

  • Generación de las claves pública y privada: el cifrado asimétrico utiliza un par de claves. La clave pública es de acceso público y cualquiera puede consultarla, mientras que la clave privada es confidencial y solo su titular puede acceder a ella. Ambas se generan mediante algoritmos matemáticos y están relacionadas entre sí, pero no se puede deducir una a partir de la otra.

  1. Alta seguridad

  • Difícil de descifrar: debido a la compleja relación matemática entre las claves pública y privada, el cifrado asimétrico ofrece un alto nivel de seguridad. A un atacante le resulta muy difícil deducir la clave privada a partir de la pública, lo que protege los datos cifrados.

  • Resistencia a los ataques de computación cuántica: algunos proyectos de blockchain han comenzado a adoptar algoritmos criptográficos resistentes a estos ataques, como variantes con seguridad cuántica del algoritmo de firma digital de curva elíptica (ECDSA), para hacer frente a la futura amenaza de la computación cuántica.

  1. La clave pública es fácil de distribuir

  • Carácter público de la clave pública: la clave pública puede compartirse con cualquiera, lo que facilita mucho su distribución. Cualquiera puede utilizarla para cifrar datos, pero solo quien posee la clave privada puede descifrarlos.

  1. Autenticación y firma digital

  • Autenticación:la clave pública puede servir como identificador del usuario, y los demás pueden verificar su identidad sin conocer su clave privada. Esto ayuda a establecer identidades fiables en redes descentralizadas como las cadenas de bloques.

  • Firma digital:el titular de la clave privada puede utilizarla para firmar un mensaje, y los demás pueden utilizar la clave pública para verificar la autenticidad de la firma. Esto garantiza la integridad del mensaje y la fiabilidad de su origen.

  1. Comunicación segura

  • Comunicación cifrada: la clave pública permite cifrar mensajes que solo puede descifrar quien posea la clave privada correspondiente. Esto garantiza la seguridad de los datos durante la comunicación e impide que personas no autorizadas accedan a la información.

  1. Control de activos digitales

  • Dirección de blockchain y clave privada:La dirección de blockchain suele derivarse de una clave pública, y el usuario controla mediante la clave privada los activos digitales asociados a esa dirección. La seguridad de la clave privada es fundamental para la seguridad de los activos digitales.

  1. Velocidad de cifrado y descifrado

  • Relativamente lento:el cifrado y descifrado asimétricos son mucho más lentos que los simétricos, ya que requieren cálculos matemáticos más complejos. Sin embargo, esto no impide su uso en ámbitos como las comunicaciones seguras y la autenticación de identidad。

2.2 Usos del cifrado asimétrico

El cifrado asimétrico tiene dos casos de uso clásicos.

  1. Cifrado:este es el uso que mejor conocemos, cifrar con la clave pública y descifrar con la clave privada.

  2. Firma: dado que la red no es de confianza y los datos pueden alterarse durante la transmisión, las claves pública y privada pueden emplearse a la inversa: se firman los datos con la clave privada y se verifica la firma con la clave pública, garantizando su seguridad e integridad.

Respecto al segundo uso, algunos lo describen como cifrar con la clave privada y descifrar con la pública; en cualquier caso, todos sabemos que se refieren a lo mismo.

Aunque los algoritmos de cifrado asimétrico pueden utilizarse teóricamente para cifrar datos y generar firmas digitales, en la práctica su elevada complejidad computacional y su baja eficiencia se convierten en los principales obstáculos.

En comparación con los algoritmos de cifrado simétrico, el cifrado asimétrico es varios órdenes de magnitud más lento, lo que afecta enormemente a su capacidad para procesar grandes volúmenes de datos. Además, como los procesos de cifrado y descifrado de los algoritmos asimétricos están estrechamente relacionados con la longitud de la clave y no admiten modos de cifrado por bloques, solo pueden procesar pequeñas cantidades de datos que no superen la longitud de la clave, y no permiten cifrar grandes volúmenes de datos.

Para superar las limitaciones de rendimiento del cifrado asimétrico, los sistemas de cifrado modernos suelen adoptar una estrategia de cifrado híbrido, que combina las ventajas del cifrado simétrico y el asimétrico. En esta estrategia, el cifrado asimétrico se utiliza principalmente para transmitir de forma segura una clave de cifrado simétrico a la otra parte (es decir, la «negociación de claves»). Una vez que ambas partes comparten de forma segura esta clave simétrica, pueden utilizar algoritmos eficientes de cifrado simétrico para cifrar y descifrar grandes cantidades de datos. Este uso combinado no solo mejora la eficiencia del cifrado, sino que también refuerza la seguridad de las comunicaciones, y se utiliza ampliamente en diversos protocolos de comunicación segura, como SSL/TLS.

En el ámbito de las firmas digitales también se han realizado adaptaciones para mejorar la eficiencia del cifrado asimétrico. El procedimiento consiste en calcular primero un resumen de los datos originales(se pueden utilizar MD5, SHA, etc.)para obtener un valor de longitud fija. Después, ese resumen se cifra mediante un algoritmo de cifrado asimétrico para generar la firma digital. Como los algoritmos de resumen pueden reducir eficazmente datos de cualquier longitud a un resumen de longitud fija, el tamaño del resumen es siempre el mismo, independientemente del tamaño de los datos originales, y el proceso de firma mantiene su eficiencia. Para verificar la firma, basta con volver a calcular el resumen de los datos originales y compararlo con la firma descifrada para determinar rápidamente si los datos han sido manipulados.

2.3 Ejemplo de cifrado

Ejemplo de cifrado y descifrado con RSA en código Java:

public class RsaDemo {    public static void main(String[] args) {        try {            KeyPair keyPair = generateRSAKeyPair();            PublicKey publicKey = keyPair.getPublic();            PrivateKey privateKey = keyPair.getPrivate();            String originalData = "Hello, 江南一点雨!";            String encryptedData = encrypt(publicKey, originalData);            String decryptedData = decrypt(privateKey, encryptedData);

            System.out.println("加密后的数据: " + encryptedData);
            System.out.println("解密后的数据: " + decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }    public static String decrypt(PrivateKey privateKey, String encryptedData) throws Exception {        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));        return new String(decryptedBytes);
    }    public static String encrypt(PublicKey publicKey, String data) throws Exception {        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);        byte[] bytes = cipher.doFinal(data.getBytes());        return Base64.getEncoder().encodeToString(bytes);
    }    public static KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException {        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048); // 可以指定密钥长度,如2048位
        return keyGen.generateKeyPair();
    }
}

2.4 Combinación de frontend y backend

Cifrado en el frontend y descifrado en el backend

El código del backend es el mismo que en el ejemplo anterior; la diferencia es que, tras obtener la clave pública, podemos imprimirla, ya que más adelante habrá que enviarla al frontend:

public static void main(String[] args) {    try {        KeyPair keyPair = generateRSAKeyPair();        PublicKey publicKey = keyPair.getPublic();        PrivateKey privateKey = keyPair.getPrivate();
        System.out.println("公钥: " + Base64.getEncoder().encodeToString(publicKey.getEncoded()));        String originalData = "Hello, 江南一点雨!";        String encryptedData = encrypt(publicKey, originalData);        String decryptedData = decrypt(privateKey, encryptedData);
        System.out.println("加密后的数据: " + encryptedData);
        System.out.println("解密后的数据: " + decryptedData);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

El código del frontend es el siguiente:

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RSA Encryption Example</title>
    <!-- 引入jsencrypt库 -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/bin/jsencrypt.min.js"></script></head><body><script>
    // 重写前端加密方法
    function encryptData(publicKey, data) {        // 创建一个新的JSEncrypt对象
        var encryptor = new JSEncrypt();        // 设置公钥
        encryptor.setPublicKey(publicKey);        // 加密数据
        var encrypted = encryptor.encrypt(data);        return encrypted;
    }    // 示例公钥(实际使用时应该替换为服务器提供的公钥)
    var publicKey = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnP5FOXIghidLDE2FgFwzi4Y+qTdYjnc0eMoiL5i4gdOeTE+7Uu9YvDB51GcBWKD9lvmZr5rX8z0OBpwe526qsTekNpXoIVk9DB34US0HOrAXEEwpUTVuSR656cJGAwmWBkVaalQynEz4Dlzrm53zExVYueruYUuzFyuZDaQcFnl3rWUH/XDkCIe23z0R1TfT3Q2OYNKft0u56r0S/ko99utXuYJK9yowe7QGT6q4cSJwsITQTomCARAq9q+bSNuGEYa4FlYCKIKWIhKbMhz0FYIMB2fJN10GyZbbvKASqeMkuCoD2Efgd8/6uMwOaMcx9LgEkcFaQ3qgDutsPXNUswIDAQAB`;    // 要加密的数据
    var data = 'Hello, javaboy!';    // 调用加密方法
    var encryptedData = encryptData(publicKey, data);    // 输出加密后的数据
    console.log('Encrypted Data:', encryptedData);</script></body></html>

Cifrado en el backend y descifrado en el frontend

Cifrar datos con el algoritmo RSA en Java y descifrarlos en JavaScript significa que el servidor cifra con la clave pública del cliente y que este descifra con su propia clave privada. En este escenario, la clave privada del cliente puede ser robada con facilidad, por lo que no se recomienda este uso. Por eso tampoco daré un ejemplo.

Bueno, amigos, hemos vuelto a charlar sobre el cifrado simétrico y asimétrico. Yo, Song, he probado satisfactoriamente todos los ejemplos de código anteriores, así que podéis utilizarlos como referencia.


Sección de comentarios

Enviando

Elige tu tema

El tema se aplica a todo el sitio.