Перейти к основному содержимому

Поиск по CooLWJ

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

Язык

Искусство шифрования и расшифрования

I Симметричное шифрование

Симметричное и асимметричное шифрование — два разных метода шифрования, играющих важную роль в обеспечении безопасности данных и передаче информации. Ниже я отдельно представлю эти две технологии шифрования:

1.1 Симметричное шифрование(Symmetric Encryption)

Симметричное шифрование означает использование одного и того же ключа для шифрования и расшифрования. Это значит, что и отправитель, и получатель должны обладать этим ключом для выполнения операций шифрования и расшифрования.

Распространённые алгоритмы симметричного шифрования:

  • AES(усовершенствованный стандарт шифрования)

  • DES(стандарт шифрования данных)

  • 3DES(тройной алгоритм шифрования данных)

1.2 Особенности симметричного шифрования

В целом симметричное шифрование обладает следующими особенностями:

  1. Довольно высокая скорость:поскольку для шифрования и расшифрования используется один и тот же ключ, симметричное шифрование обычно быстрее асимметричного.

  2. Управление ключами:Главная трудность симметричного шифрования — распространение ключей и управление ими. При утечке ключа безопасность шифрования оказывается под угрозой.

  3. Подходит для больших объёмов данных:благодаря высокой скорости симметричное шифрование подходит для шифрования больших объёмов данных.

Отсюда видно,что у симметричного шифрования два основных преимущества:во-первых,высокая скорость вычислений;во-вторых,пригодность для больших объёмов данных。

Однако у симметричного шифрования есть одна критическая проблема — управление ключами. Как безопасно передать ключ с сервера клиенту — вот вопрос! Кроме того, при обмене данными по схеме «один ко многим» непросто организовать управление ключами так, чтобы не допустить их утечки. В этом и состоит недостаток симметричного шифрования.

Художественная иллюстрация с сине-зелёной стеной и малярным валиком

1.3 Пример кода

Далее брат Сун покажет вам, как выполнять симметричное шифрование и расшифрование в коде Java.

Для реализации симметричного шифрования в Java обычно используют классы и интерфейсы, предоставляемые криптографической архитектурой Java (Java Cryptography Architecture, JCA).

Ниже приведён простой пример симметричного шифрования и расшифрования с использованием алгоритма AES(расширенный стандарт шифрования):

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. Создание ключей:использование KeyGenerator сгенерировать ключ AES.

  2. Шифрование:использование Cipher класс для шифрования: данные преобразуются в байты и шифруются, а затем кодируются в Base64 и превращаются в строку для удобства хранения или передачи.

  3. Расшифровка:декодировать зашифрованную строку обратно в байты, затем расшифровать их тем же ключом.

В приведённом выше коде следует обратить внимание на следующее:

  • Длину ключа(например, 128 бит)следует выбирать в соответствии с требованиями безопасности.

  • Обеспечьте безопасное хранение ключей, не прописывайте их непосредственно в коде.

  • Для производственной среды следует рассмотреть более безопасную стратегию управления ключами.

Из соображений безопасности мы обычно генерируем ключ указанным выше способом. У ключа, созданного таким способом, есть особенность: он меняется при каждом перезапуске системы. Если вы хотите самостоятельно управлять генерацией ключа, можно создать его следующим образом:

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");
}

Так можно управлять ключом с помощью передаваемых вами параметров.

1.4 Взаимодействие клиентской и серверной частей

При шифровании и расшифровании алгоритмом AES в разных языках (например, JavaScript и Java) главное — убедиться, что обе стороны используют одинаковые ключ, режим алгоритма (например, CBC, ECB и т. д.), схему дополнения (например, PKCS5Padding, PKCS7Padding и т. д.) и вектор инициализации (IV, если используется режим, требующий IV, например CBC).

Ранее кто-то из читателей писал, что после шифрования на клиентской стороне сервер никак не может расшифровать данные. Поэтому я, Сун-гэ, приведу пример совместной работы клиентской и серверной частей.

Шифрование на фронтенде, расшифровка на бэкенде

Код фронтенда:

<!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>


Код серверной части:

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();
        }
    }
}

Обратите внимание: при шифровании на стороне клиента создаётся параметр iv, который нужно передать на сервер вместе с результатом шифрования.

Шифрование на сервере, расшифровка на клиенте

При шифровании AES в Java и расшифровке в JavaScript также необходимо убедиться, что обе стороны используют одинаковый ключ, режим алгоритма(например, CBC, ECB и т. д.), схему дополнения(например, PKCS5Padding, PKCS7Padding и т. д.), а также(если применимо)одинаковый вектор инициализации(IV).

Код серверной части:

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();
        }
    }
}

Код фронтенда:

<!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>

Сунгэ лично проверил приведённый выше код шифрования и расшифрования при взаимодействии клиентской и серверной частей — всё работает без проблем. Если вам нужно что-то подобное, сохраните статью в закладки: она пригодится как справочный материал.

II Асимметричное шифрование

2.1 Что такое асимметричное шифрование

Асимметричное шифрование, также называемое шифрованием с открытым ключом, — это способ шифрования с использованием двух разных ключей(открытого и закрытого).

В целом асимметричное шифрование обладает следующими отличительными особенностями:

  1. Открытый и закрытый ключи

  • Создание открытого и закрытого ключей: асимметричное шифрование использует пару ключей. Открытый ключ общедоступен, им может воспользоваться любой, а закрытый является личным и доступен только владельцу. Эти ключи создаются математическим алгоритмом и связаны друг с другом, однако вывести один из другого невозможно.

  1. Высокая безопасность

  • Трудно взломать:благодаря сложной математической связи между открытым и закрытым ключами асимметричное шифрование обеспечивает высокий уровень безопасности. Злоумышленнику очень трудно вывести закрытый ключ из открытого, что обеспечивает безопасность зашифрованных данных.

  • Устойчивость к атакам квантовых компьютеров:некоторые блокчейн-проекты начинают применять криптографические алгоритмы, устойчивые к атакам квантовых компьютеров, например квантово-безопасные варианты алгоритма цифровой подписи на эллиптических кривых(ECDSA), чтобы противостоять будущей угрозе квантовых вычислений.

  1. Удобство распространения открытого ключа

  • Открытость публичного ключа:публичный ключ можно сообщить кому угодно, поэтому распространять его очень удобно. Любой может зашифровать данные с помощью публичного ключа, но расшифровать их сможет только владелец закрытого ключа.

  1. Аутентификация и цифровая подпись

  • Проверка подлинности: открытый ключ может служить идентификатором пользователя, позволяя другим проверить его личность без знания закрытого ключа. Это помогает создавать доверенные цифровые идентичности в децентрализованных сетях, таких как блокчейн.

  • Цифровая подпись:владелец закрытого ключа может подписать им сообщение, а другие — проверить подлинность подписи с помощью открытого ключа. Это обеспечивает целостность сообщения и достоверность его происхождения.

  1. Защищённая связь

  • Шифрование связи: открытый ключ можно использовать для шифрования сообщений, а расшифровать их может только обладатель соответствующего закрытого ключа. Это обеспечивает безопасность данных при передаче и предотвращает доступ к информации посторонних лиц.

  1. Управление цифровыми активами

  • Адрес блокчейна и закрытый ключ:адрес блокчейна обычно выводится из открытого ключа, а пользователь управляет связанными с этим адресом цифровыми активами с помощью закрытого ключа. Защищённость закрытого ключа критически важна для безопасности цифровых активов.

  1. Скорость шифрования и расшифрования

  • Относительно низкая скорость: шифрование и расшифрование при асимметричном шифровании выполняются значительно медленнее, чем при симметричном, поскольку требуют более сложных математических вычислений. Однако это не мешает его применению для защищённой связи, аутентификации и в других областях.

2.2 Применение асимметричного шифрования

У асимметричного шифрования есть два классических сценария применения.

  1. Шифрование: самое знакомое нам применение — шифрование открытым ключом и расшифровка закрытым.

  2. Подпись: поскольку сети нельзя доверять и данные могут быть изменены при передаче, открытый и закрытый ключи можно использовать в обратном порядке: подписать данные закрытым ключом, а открытым проверить подпись, обеспечив безопасность и целостность данных.

Говоря о втором назначении, некоторые описывают его как шифрование закрытым ключом и расшифровку открытым; в любом случае все понимают, что речь об одном и том же.

Хотя асимметричные алгоритмы шифрования теоретически могут применяться для шифрования данных и цифровых подписей, на практике основными препятствиями становятся их высокая вычислительная сложность и низкая эффективность.

По сравнению с алгоритмами симметричного шифрования асимметричное шифрование работает на несколько порядков медленнее, что существенно ограничивает его способность обрабатывать большие объёмы данных. Кроме того, поскольку процессы шифрования и расшифрования в асимметричных алгоритмах тесно связаны с длиной ключа и не поддерживают режимы блочного шифрования, они могут обрабатывать лишь небольшие объёмы данных, не превышающие длину ключа, и не позволяют шифровать большие объёмы данных.

Чтобы преодолеть ограничения производительности асимметричного шифрования, современные криптографические системы обычно применяют гибридную стратегию, сочетающую преимущества симметричного и асимметричного шифрования. В этой стратегии асимметричное шифрование главным образом используется для безопасной передачи другой стороне ключа симметричного шифрования(то есть «согласования ключей»). Как только стороны безопасно получили общий симметричный ключ, они могут использовать эффективные алгоритмы симметричного шифрования для шифрования и расшифрования больших объёмов данных. Такой комбинированный подход не только повышает эффективность шифрования, но и усиливает безопасность связи; он широко применяется в различных протоколах защищённой связи, например SSL/TLS.

В области цифровых подписей также были сделаны некоторые адаптации для повышения эффективности асимметричного шифрования. Сначала для исходных данных вычисляют хеш (например, с помощью MD5, SHA и других алгоритмов), получая значение фиксированной длины. Затем это значение шифруют асимметричным алгоритмом, создавая цифровую подпись. Поскольку алгоритм хеширования позволяет эффективно свести данные любой длины к хешу фиксированной длины, размер хеша остаётся одинаковым независимо от размера исходных данных, а процесс подписания сохраняет эффективность. При проверке подписи достаточно заново вычислить хеш исходных данных и сравнить его с расшифрованной подписью, чтобы быстро определить, были ли данные изменены.

2.3 Пример шифрования

Пример шифрования и расшифрования с RSA в коде 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 Сочетание фронтенда и бэкенда

Шифрование на фронтенде, расшифровка на бэкенде

Серверный код такой же, как в примере выше. Отличие в том, что после получения открытого ключа мы можем вывести его на печать: впоследствии этот ключ потребуется передать клиентской части:

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();
    }
}

Код фронтенда приведён ниже:

<!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>

Шифрование на сервере, расшифровка на клиенте

Шифрование данных алгоритмом RSA в Java и их расшифровка в JavaScript означают, что сервер шифрует данные открытым ключом клиентской части, а клиентская часть расшифровывает их своим закрытым ключом. В таком сценарии закрытый ключ клиентской части очень легко украсть, поэтому я не рекомендую этот способ и не буду приводить пример.

Ну вот, друзья, мы ещё раз поговорили о симметричном и асимметричном шифровании. Я, Сун-гэ, проверил весь приведённый выше код примеров — он работает, так что можете использовать его как ориентир.


Комментарии

Отправка

Выберите оформление

Выбранное оформление применяется ко всему сайту.