Java加密算法之公钥/私钥非对称加密【3】

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(84)   2024-05-24 16:20:43

抽象的

这是涵盖 Java 加密算法的三部分博客系列的第 3 部分。该系列涵盖了如何实现以下内容:

  1. 使用 SHA–512 进行哈希处理
  2. 使用AES–256的单密钥对称加密
  3. RSA–4096

第 3 篇文章详细介绍了如何实现公钥/私钥、非对称、RSA–4096 加密。让我们开始吧。

免责声明

这篇文章仅供参考。在使用所提供的任何信息之前,请认真思考。从中吸取教训,但最终做出自己的决定需要您自担风险。

要求

我使用以下主要技术完成了这篇文章的所有工作。您也许可以使用不同的技术或版本做同样的事情,但不能保证。

  • Java 1.8.0_152_x64
  • NetBeans 8.2(内部版本 201609300101)
  • Maven 3.0.5(与 NetBeans 捆绑)

下载

访问我的 GitHub 页面,查看我所有的开源项目。这篇文章的代码位于项目中:thoth-cryptography

非对称加密

关于

非对称算法基于 2 个密钥:公钥和私钥。公钥负责加密,私钥负责解密。公钥可以自由分发。使用公钥,任何客户端都可以加密只有您使用私钥才能解密的消息(非对称算法,n.d. 第 3 段)。

非对称算法是互联网的主力军。 SSH、OpenPGP、SSL 和 TLS 等协议依赖于非对称算法(Rouse,2016 年,第 2 段)。任何使用 Web 浏览器进行网上银行之类的事情的人都天生就知道非对称算法的重要性。

截至今天所做的研究似乎表明最好和最安全的公钥/私钥、非对称加密算法如下(Sheth,2017 年,“选择正确的算法”,第 2 段):

  1. 算法:RSA
  2. 模式:ECB // 确实没有,但需要 ECB 才能让 Java 工作。
  3. 填充:OAEPWithSHA–512AndMGF1Padding
  4. 密钥大小:4096 位

RSA 不是块密码,因此 ECB 模式没有多大意义,但是,需要 ECB 才能使 Java 工作,即使该模式并未在幕后使用(Brightwell,2015)。 OAEP 提供高水平的随机性和填充。让我们看一个例子。

例子

清单 1 是 RsaTest.java 单元测试。这是对以下内容的完整演示:

  1. 生成并存储 RSA 4096 位密钥
  2. RSA 加密
  3. RSA解密

清单 2 显示了 RsaKeyPairProducer.java。这是一个帮助程序类,负责生成一个新的 KeyPairKeyPair 包含 PublicKeyPrivateKey。清单 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 类

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 mjremijan@yahoo.com @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 类

package org.thoth.crypto.asymmetric;

import java.security.KeyPair;
import java.security.KeyPairGenerator;

/**
 *
 * @author Michael Remijan mjremijan@yahoo.com @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 类

package org.thoth.crypto.asymmetric;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;

/**
 *
 * @author Michael Remijan mjremijan@yahoo.com @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 类

package org.thoth.crypto.asymmetric;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;

/**
 *
 * @author Michael Remijan mjremijan@yahoo.com @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 类

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 mjremijan@yahoo.com @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 类

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 mjremijan@yahoo.com @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 类

package org.thoth.crypto.asymmetric;

import java.security.PublicKey;
import javax.crypto.Cipher;

/**
 *
 * @author Michael Remijan mjremijan@yahoo.com @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 类

package org.thoth.crypto.asymmetric;

import java.security.PrivateKey;
import javax.crypto.Cipher;

/**
 *
 * @author Michael Remijan mjremijan@yahoo.com @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);
        }
    }

}

概括

加密并不容易。简单的示例将导致您的应用程序具有安全漏洞的实现。如果您需要公钥/私钥、非对称加密算法,请使用带有 4096 位密钥的 RSA/ECB/OAEPWithSHA–512AndMGF1Padding。

参考

Sheth, M.(2017 年,4 月 18 日)。 Java密码学中的加密和解密。取自 https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography

  • 最佳算法、模式和填充
  • 使用 4096 位密钥

Brightwell, W.,斗篷。 (2015 年 5 月 4 日)。 ECB 模式与 RSA 加密一起使用是否安全?取自 https://crypto.stackexchange.com/questions/25420/is-ecb-mode-safe-to-use-with-rsa-encryption

  • ECB 不适用; Java 不会在幕后使用它。
  • RSA 使用随机性和填充来避免 ECB 问题相同的明文生成相同的密文

玛丽莲娜。 (2016 年,11 月 29 日)。 Java – 非对称加密示例。取自 https://www.mkyong.com/java/java-asymmetric-cryptography-example/

  • 将公钥/私钥写入文件
  • 从文件中读取公钥/私钥
  • 加密和解密

密钥大小。 (2017 年,10 月 12 日)。维基百科,免费的百科全书。取自“https://en.wikipedia.org/wiki/Key_size。

  • 2048 位密钥在 2030 年之前就足够了
  • 如果需要 2030 年后的安全性,则应使用 3072 位的 RSA 密钥长度

用户4982。 (2013 年,11 月 4 日)。 IV 如何与 RSA 加密结合使用?。从 https://crypto.stackexchange.com/questions/11403/how-are-ivs-used-in-association-with-rsa-encryption 中检索。

  • IV 值不用于 RSA
标签2: Java教程
地址:https://www.cundage.com/article/jcg-choosing-java-cryptographic-algorithms-part-3-public-private-key-asymmetric-encryption.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...