Статьи

Выбор криптографических алгоритмов Java, часть 3 — асимметричное шифрование с открытым / закрытым ключом

Аннотация

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

  1. Хеширование с помощью SHA – 512
  2. Симметричное шифрование с одним ключом с AES-256
  3. RSA-4096

В этом третьем посте подробно описывается, как реализовать открытый / закрытый ключ, асимметричное шифрование RSA-4096. Давайте начнем.

отказ

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

Требования

Я сделал всю работу для этого поста, используя следующие основные технологии. Вы можете сделать то же самое с разными технологиями или версиями, но без гарантий.

  • Java 1.8.0_152_x64
  • NetBeans 8.2 (сборка 201609300101)
  • Maven 3.0.5 (в комплекте с NetBeans)

Скачать

Посетите мою страницу GitHub, чтобы увидеть все мои проекты с открытым исходным кодом. Код для этого поста находится в проекте: thoth-cryptography

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

Около

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

Асимметричные алгоритмы — рабочая лошадка Интернета. Протоколы, такие как SSH, OpenPGP, SSL и TLS, основаны на асимметричных алгоритмах (Rouse, 2016, пункт 2). Любой, кто использует веб-браузер для чего-то вроде онлайн-банкинга, по своей сути знает важность асимметричных алгоритмов.

Исследования, проведенные на сегодняшний день, указывают на то, что наилучшим и наиболее безопасным алгоритмом шифрования с открытым / закрытым ключом является асимметричный алгоритм (Sheth, 2017, «Выбор правильного алгоритма», пункт 2):

  1. Алгоритм: RSA
  2. Режим: ECB // Это действительно НЕТ, но ECB необходим, чтобы заставить Java работать.
  3. Заполнение: OAEPWHA-512AndMGF1Padding
  4. Размер ключа: 4096 бит

RSA не является блочным шифром, поэтому режим ECB не имеет особого смысла, но ECB необходим, чтобы заставить Java работать, даже если этот режим не используется под прикрытием (Brightwell, 2015). OAEP обеспечивает высокий уровень случайности и заполнения. Давайте посмотрим на пример.

пример

В листинге 1 приведен модульный тест RsaTest.java . Это полная демонстрация следующего:

  1. Создайте и сохраните 4096-битный ключ RSA
  2. RSA шифрование
  3. Расшифровка RSA

В листинге 2 показан RsaKeyPairProducer.java . Это вспомогательный класс, который отвечает за создание новой KeyPair . KeyPair содержит как PublicKey и PrivateKey .
В листинге 3 показан RsaPrivateKeyProducer.java . Это вспомогательный класс, который отвечает за воспроизведение PrivateKey из byte[] .

В листинге 4 показан RsaPublicKeyProducer.java . Это вспомогательный класс, который отвечает за воспроизведение PublicKey из byte[] .

В листинге 5 показан ByteArrayWriter.java, а в листинге 6 — ByteArrayReader.java . Это вспомогательные классы, отвечающие за чтение и запись byte[] в файл. Вам решать, как хранить byte[] ваших ключей, но его нужно надежно хранить где-то (файл, база данных, репозиторий git и т. Д.).

В листинге 7 показан RsaEncrypter.java . Это вспомогательный класс, который отвечает за шифрование.

Наконец, листинг 8 показывает RsaDecrypter.java . Это вспомогательный класс, который отвечает за расшифровку.

Листинг 1 — класс RsaTest.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package org.thoth.crypto.asymmetric;
 
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.thoth.crypto.io.ByteArrayReader;
import org.thoth.crypto.io.ByteArrayWriter;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class RsaTest {
 
    static Path privateKeyFile;
    static Path publicKeyFile;
 
    @BeforeClass
    public static void beforeClass() throws Exception {
 
        // Store the PrivateKey and PublicKey bytes in the ./target
        // diretory. Do this so it will be ignore by source control.
        // We don't want this file committed.
        privateKeyFile
            = Paths.get("./target/RsaPrivate.key").toAbsolutePath();
        publicKeyFile
            = Paths.get("./target/RsaPublic.key").toAbsolutePath();
 
        // Create KeyPair for RSA
        KeyPair keyPair
            = new RsaKeyPairProducer().produce();
 
        // Store the PrivateKey bytes. This is what
        // you want to keep absolutely safe
        {
            ByteArrayWriter writer = new ByteArrayWriter(privateKeyFile);
            writer.write(keyPair.getPrivate().getEncoded());
        }
 
        // Store the PublicKey bytes.  This you
        // can freely distribute so others can
        // encrypt messages which you can then
        // decrypt with the PrivateKey you keep safe.
        {
            ByteArrayWriter writer = new ByteArrayWriter(publicKeyFile);
            writer.write(keyPair.getPublic().getEncoded());
        }
    }
 
 
    @Test
    public void encrypt_and_decrypt() throws Exception {
        // setup
        PrivateKey privateKey
            = new RsaPrivateKeyProducer().produce(
                new ByteArrayReader(privateKeyFile).read()
            );
 
        PublicKey publicKey
            = new RsaPublicKeyProducer().produce(
                new ByteArrayReader(publicKeyFile).read()
            );
 
        RsaDecrypter decrypter
            = new RsaDecrypter(privateKey);
 
        RsaEncrypter encrypter
            = new RsaEncrypter(publicKey);
 
        String toEncrypt
            = "encrypt me";
 
        // run
        byte[] encryptedBytes
            = encrypter.encrypt(toEncrypt);
        System.out.printf("Encrypted %s%n", new String(encryptedBytes,"UTF-8"));
 
        String decrypted
            = decrypter.decrypt(encryptedBytes);
 
        // assert
        Assert.assertEquals(toEncrypt, decrypted);
    }
 
}

Листинг 2 — класс RsaKeyPairProducer.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package org.thoth.crypto.asymmetric;
 
import java.security.KeyPair;
import java.security.KeyPairGenerator;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class RsaKeyPairProducer {
 
    /**
     * Generates a new RSA-4096 bit {@code KeyPair}.
     *
     * @return {@code KeyPair}, never null
     * @throws RuntimeException All exceptions are caught
     * and re-thrown as {@code RuntimeException}
     */
    public KeyPair produce() {
        KeyPairGenerator keyGen;
        try {
            keyGen = KeyPairGenerator.getInstance("RSA");
            //keyGen.initialize(3072);
            keyGen.initialize(4096);
            return keyGen.generateKeyPair();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

Листинг 3 — класс RsaPrivateKeyProducer.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.thoth.crypto.asymmetric;
 
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class RsaPrivateKeyProducer {
 
    /**
     * Regenerates a previous RSA {@code PrivateKey}.
     *
     * @param encodedByteArrayForPrivateKey The bytes this method
     * will use to regenerate a previously created {@code PrivateKey}
     *
     * @return {@code PrivateKey}, never null
     * @throws RuntimeException All exceptions are caught
     * and re-thrown as {@code RuntimeException}
     */
    public PrivateKey produce(byte[] encodedByteArrayForPrivateKey) {
        try {
            PrivateKey privateKey = KeyFactory.getInstance("RSA")
                .generatePrivate(new PKCS8EncodedKeySpec(encodedByteArrayForPrivateKey));
 
            return privateKey;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

Листинг 4 — Класс RsaPublicKeyProducer.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.thoth.crypto.asymmetric;
 
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class RsaPublicKeyProducer {
 
    /**
     * Regenerates a previous RSA {@code PublicKey}.
     *
     * @param encodedByteArrayForPublicKey The bytes this method
     * will use to regenerate a previously created {@code PublicKey}
     *
     * @return {@code PublicKey}, never null
     * @throws RuntimeException All exceptions are caught
     * and re-thrown as {@code RuntimeException}
     */
    public PublicKey produce(byte[] encodedByteArrayForPublicKey) {
        try {
            PublicKey publicKey = KeyFactory.getInstance("RSA")
                .generatePublic(new X509EncodedKeySpec(encodedByteArrayForPublicKey));
 
            return publicKey;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

Листинг 5 — Класс ByteArrayWriter.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package org.thoth.crypto.io;
 
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class ByteArrayWriter {
 
    protected Path outputFile;
 
    private void initOutputFile(Path outputFile) {
        this.outputFile = outputFile;
    }
 
    private void initOutputDirectory() {
        Path outputDirectory = outputFile.getParent();
        if (!Files.exists(outputDirectory)) {
            try {
                Files.createDirectories(outputDirectory);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
 
    public ByteArrayWriter(Path outputFile) {
        initOutputFile(outputFile);
        initOutputDirectory();
    }
 
    public void write(byte[] bytesArrayToWrite) {
        try (
            OutputStream os
                = Files.newOutputStream(outputFile);
 
            PrintWriter writer
                new PrintWriter(os);
        ){
            for (int i=0; i<bytesArrayToWrite.length; i++) {
                if (i>0) {
                    writer.println();
                }
                writer.print(bytesArrayToWrite[i]);
            }
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
}

Листинг 6 — Класс ByteArrayReader.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.thoth.crypto.io;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Scanner;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class ByteArrayReader {
 
    protected Path inputFile;
 
    public ByteArrayReader(Path inputFile) {
        this.inputFile = inputFile;
    }
 
    public byte[] read() {
        try (
            Scanner scanner
                new Scanner(inputFile);
 
            ByteArrayOutputStream baos
                = new ByteArrayOutputStream();
        ){
            while (scanner.hasNext()) {
                baos.write(Byte.parseByte(scanner.nextLine()));
            }
             
            baos.flush();
            return baos.toByteArray();
 
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
}

Листинг 7 — Класс RsaEncrypter.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.thoth.crypto.asymmetric;
 
import java.security.PublicKey;
import javax.crypto.Cipher;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class RsaEncrypter {
 
    protected RsaCipher cipher;
 
    public RsaEncrypter(PublicKey key) {
        this.cipher = new RsaCipher(Cipher.ENCRYPT_MODE, key);
    }
 
    public byte[] encrypt(String message) {
        try {
            return cipher
                    .update(message.getBytes("UTF-8"))
                    .doFinal()
            ;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Листинг 8 — Класс RsaDecrypter.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.thoth.crypto.asymmetric;
 
import java.security.PrivateKey;
import javax.crypto.Cipher;
 
/**
 *
 * @author Michael Remijan [email protected] @mjremijan
 */
public class RsaDecrypter {
 
    protected RsaCipher cipher;
 
    public RsaDecrypter(PrivateKey key) {
        this.cipher = new RsaCipher(Cipher.DECRYPT_MODE, key);
    }
 
    public String decrypt(byte[] message) {
        try {
            return new String(
                cipher.update(message).doFinal()
                , "UTF-8"
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
 
}

Резюме

Шифрование не легко. И простые примеры приведут к реализации с уязвимостями безопасности для вашего приложения. Если вам нужен открытый / закрытый ключ, асимметричный алгоритм шифрования, используйте RSA / ECB / OAEPWithSHA – 512AndMGF1Padding с 4096-битным ключом.

Рекомендации

Шет М. (2017, 18 апреля). Шифрование и дешифрование в криптографии Java. Получено с https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography .

  • Лучшие алгоритмы, режимы и отступы
  • Используйте 4096-битный ключ

Брайтвелл В. Пончо. (2015, 4 мая). Безопасен ли режим ECB для шифрования RSA? Получено с https://crypto.stackexchange.com/questions/25420/is-ecb-mode-safe-to-use-with-rsa-encryption .

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

Marilena. (2016, 29 ноября). Java — пример асимметричной криптографии. Получено с https://www.mkyong.com/java/java-asymmetric-cryptography-example/ .

  • Запись открытых / закрытых ключей в файл
  • Чтение открытых / закрытых ключей из файла
  • Зашифровать и расшифровать

Размер ключа. (2017, 12 октября). Wikipedia. Получено с https://en.wikipedia.org/wiki/Key_size .

  • 2048-битных ключей достаточно до 2030 года
  • Длина ключа RSA 3072 бит должна использоваться, если требуется защита после 2030 года

user4982. (2013, 4 ноября). Как IV используются в сочетании с шифрованием RSA? Получено с https://crypto.stackexchange.com/questions/11403/how-are-ivs-used-in-association-with-rsa-encryption .

  • Значения IV не используются с RSA

Опубликовано на Java Code Geeks с разрешения Майкла Ремиджана, партнера нашей программы JCG. См. Оригинальную статью здесь: Выбор алгоритмов шифрования Java. Часть 3. Асимметричное шифрование с открытым / закрытым ключом.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.