Java - 使用现有公钥文件加密字符串

2022-09-02 14:22:25

在过去的4-5个小时里,我一直在研究这个问题,尽管找到了从几种方法到整个约100行类的“答案”,但似乎找不到一个真正有效的答案。我无法想象没有一些简单的功能来做这样一件微不足道的事情:P

我有一组预先存在的公钥/私钥(实际上,两组 - 一组由ssh-keygen生成,另一组由openssl生成,因此......无论格式如何工作都很酷)。

我所追求的只是一个简单的java,相当于我用python编写的东西,比如 -

key_object = someModule.KeyObject(nameOfPublicKeyFile)

def encrypt (SomePlainText) :
  return someOtherModule.encrypt(key_object, SomePlainText)

任何帮助都会很棒!


答案 1

shell 中的这些 openssl 命令创建一个 RSA 密钥对,并将公钥和私钥写入 DER 格式的文件。

在这里,私钥文件不受密码保护(-nocrypt),以保持简单。

$ openssl genrsa -out keypair.pem 2048
Generating RSA private key, 2048 bit long modulus
............+++
................................+++
e is 65537 (0x10001)
$ openssl rsa -in keypair.pem -outform DER -pubout -out public.der
writing RSA key
$ openssl pkcs8 -topk8 -nocrypt -in keypair.pem -outform DER -out private.der

现在您已经有了 DER 文件,您可以在 Java 中读取它们,并使用 KeySpecKeyFactory 创建公钥私钥对象。

public byte[] readFileBytes(String filename) throws IOException
{
    Path path = Paths.get(filename);
    return Files.readAllBytes(path);        
}

public PublicKey readPublicKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
    X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(readFileBytes(filename));
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return keyFactory.generatePublic(publicSpec);       
}

public PrivateKey readPrivateKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(readFileBytes(filename));
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return keyFactory.generatePrivate(keySpec);     
}

使用公钥和私钥,您可以加密和解密少量数据(适合 RSA 模数)。我建议使用OAEP填充。

public byte[] encrypt(PublicKey key, byte[] plaintext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");   
    cipher.init(Cipher.ENCRYPT_MODE, key);  
    return cipher.doFinal(plaintext);
}

public byte[] decrypt(PrivateKey key, byte[] ciphertext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");   
    cipher.init(Cipher.DECRYPT_MODE, key);  
    return cipher.doFinal(ciphertext);
}

在这里,它与简单的加密和解密联系在一起:

public void Hello()
{
    try
    {
        PublicKey publicKey = readPublicKey("public.der");
        PrivateKey privateKey = readPrivateKey("private.der");
        byte[] message = "Hello World".getBytes("UTF8");
        byte[] secret = encrypt(publicKey, message);
        byte[] recovered_message = decrypt(privateKey, secret);
        System.out.println(new String(recovered_message, "UTF8"));
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

答案 2

我想分享一段代码。好吧,实际上一个完整的类,如果你根据自己的需求自定义它,它可以做你需要的事情。我在我的一个应用程序中使用它,我曾经使用生成的公钥/私钥加密/解密文件。这同样可以应用于字符串。

import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.*;
import java.util.*;

/**
 *  This class encrypts and decrypts a file using CipherStreams
 *  and a 256-bit Rijndael key. The key is then encrypted using
 *  a 1024-bit RSA key, which is password-encrypted.
 */
public class FileEncryptorRSA {
  /**
   *    When files are encrypted, this will be appended to the end
   *    of the filename.
   */
  private static final String ENCRYPTED_FILENAME_SUFFIX=".encrypted";

  /**
   *    When files are decrypted, this will be appended to the end
   *    of the filename.
   */
  private static final String DECRYPTED_FILENAME_SUFFIX=".decrypted";

  /**
   *    Number of times the password will be hashed with MD5
   *    when transforming it into a TripleDES key.
   */
  private static final int ITERATIONS = 1000;

  /**
   *    FileEncryptor is started with one of three options:
   *
   *    -c: create key pair and write it to 2 files
   *    -e: encrypt a file, given as an argument
   *    -d: decrypt a file, given as an argument
   */
  public static void main (String[] args)
  throws Exception {
    if ((args.length < 1) || (args.length > 2)) {
      usage();
    } else if ("-c".equals(args[0])) {
      createKey();
    } else if ("-e".equals(args[0])) {
      encrypt(args[1]);
    } else if ("-d".equals(args[0])) {
      decrypt(args[1]);
    } else {
      usage();
    }
  }

  private static void usage() {
    System.err.println("Usage: java FileEncryptor -c|-e|-d [filename]");
    System.exit(1);
  }

  /**
   *    Creates a 1024 bit RSA key and stores it to
   *    the filesystem as two files.
   */
  private static void createKey()
  throws Exception {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    System.out.print("Password to encrypt the private key: ");
    String password = in.readLine();
    System.out.println("Generating an RSA keypair...");

    // Create an RSA key
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    keyPairGenerator.initialize(1024);
    KeyPair keyPair = keyPairGenerator.genKeyPair();

    System.out.println("Done generating the keypair.\n");

    // Now we need to write the public key out to a file
    System.out.print("Public key filename: ");
    String publicKeyFilename = in.readLine();

    // Get the encoded form of the public key so we can
    // use it again in the future. This is X.509 by default.
    byte[] publicKeyBytes = keyPair.getPublic().getEncoded();

    // Write the encoded public key out to the filesystem
    FileOutputStream fos = new FileOutputStream(publicKeyFilename);
    fos.write(publicKeyBytes);
    fos.close();

    // Now we need to do the same thing with the private key,
    // but we need to password encrypt it as well.
    System.out.print("Private key filename: ");
    String privateKeyFilename = in.readLine();

    // Get the encoded form. This is PKCS#8 by default.
    byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();

    // Here we actually encrypt the private key
    byte[] encryptedPrivateKeyBytes =
    passwordEncrypt(password.toCharArray(),privateKeyBytes);

    fos = new FileOutputStream(privateKeyFilename);
    fos.write(encryptedPrivateKeyBytes);
    fos.close();
  }

  /**
   *    Encrypt the given file with a session key encrypted with an
   *    RSA public key which will be read in from the filesystem.
   */
  private static void encrypt(String fileInput)
  throws Exception {

    BufferedReader in = new BufferedReader
    (new InputStreamReader(System.in));
    System.out.print("Public Key to encrypt with: ");
    String publicKeyFilename = in.readLine();

    // Load the public key bytes
    FileInputStream fis = new FileInputStream(publicKeyFilename);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    int theByte = 0;
    while ((theByte = fis.read()) != -1)
    {
      baos.write(theByte);
    }
    fis.close();

    byte[] keyBytes = baos.toByteArray();
    baos.close();

    // Turn the encoded key into a real RSA public key.
    // Public keys are encoded in X.509.
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(keySpec);

    // Open up an output file for the output of the encryption
    String fileOutput = fileInput + ENCRYPTED_FILENAME_SUFFIX;
    DataOutputStream output = new DataOutputStream
    (new FileOutputStream(fileOutput));

    // Create a cipher using that key to initialize it
    Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);

    // Now create a new 256 bit Rijndael key to encrypt the file itself.
    // This will be the session key.
    KeyGenerator rijndaelKeyGenerator = KeyGenerator.getInstance("Rijndael");
    rijndaelKeyGenerator.init(256);
    System.out.println("Generating session key...");
    Key rijndaelKey = rijndaelKeyGenerator.generateKey();
    System.out.println("Done generating key.");

    // Encrypt the Rijndael key with the RSA cipher
    // and write it to the beginning of the file.
    byte[] encodedKeyBytes= rsaCipher.doFinal(rijndaelKey.getEncoded());
    output.writeInt(encodedKeyBytes.length);
    output.write(encodedKeyBytes);

    // Now we need an Initialization Vector for the symmetric cipher in CBC mode
    SecureRandom random = new SecureRandom();
    byte[] iv = new byte[16];
    random.nextBytes(iv);

    // Write the IV out to the file.
    output.write(iv);
    IvParameterSpec spec = new IvParameterSpec(iv);

    // Create the cipher for encrypting the file itself.
    Cipher symmetricCipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
    symmetricCipher.init(Cipher.ENCRYPT_MODE, rijndaelKey, spec);

    CipherOutputStream cos = new CipherOutputStream(output, symmetricCipher);

    System.out.println("Encrypting the file...");

    FileInputStream input = new FileInputStream(fileInput);

    theByte = 0;
    while ((theByte = input.read()) != -1)
    {
      cos.write(theByte);
    }
    input.close();
    cos.close();
    System.out.println("File encrypted.");
    return;
  }

  /**
   *    Decrypt the given file.
   *    Start by getting the RSA private key
   *    and decrypting the session key embedded
   *    in the file. Then decrypt the file with
   *    that session key.
   */
  private static void decrypt(String fileInput)
  throws Exception {

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    System.out.print("Private Key to decrypt with: ");
    String privateKeyFilename = in.readLine();

    System.out.print("Password for the private key: ");
    String password = in.readLine();

    // Load the private key bytes
    FileInputStream fis = new FileInputStream(privateKeyFilename);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    int theByte = 0;
    while ((theByte = fis.read()) != -1)
    {
      baos.write(theByte);
    }
    fis.close();

    byte[] keyBytes = baos.toByteArray();
    baos.close();

    keyBytes = passwordDecrypt(password.toCharArray(), keyBytes);

    // Turn the encoded key into a real RSA private key.
    // Private keys are encoded in PKCS#8.
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

    // Create a cipher using that key to initialize it
    Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

    // Read in the encrypted bytes of the session key
    DataInputStream dis = new DataInputStream(new FileInputStream(fileInput));
    byte[] encryptedKeyBytes = new byte[dis.readInt()];
    dis.readFully(encryptedKeyBytes);

    // Decrypt the session key bytes.
    rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] rijndaelKeyBytes = rsaCipher.doFinal(encryptedKeyBytes);

    // Transform the key bytes into an actual key.
    SecretKey rijndaelKey = new SecretKeySpec(rijndaelKeyBytes, "Rijndael");

    // Read in the Initialization Vector from the file.
    byte[] iv = new byte[16];
    dis.read(iv);
    IvParameterSpec spec = new IvParameterSpec(iv);

    Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, rijndaelKey, spec);
    CipherInputStream cis = new CipherInputStream(dis, cipher);

    System.out.println("Decrypting the file...");
    FileOutputStream fos = new FileOutputStream(fileInput + DECRYPTED_FILENAME_SUFFIX);

    // Read through the file, decrypting each byte.
    theByte = 0;
    while ((theByte = cis.read()) != -1)
    {
      fos.write(theByte);
    }
    cis.close();
    fos.close();
    System.out.println("Done.");
    return;
  }

  /**
   *    Utility method to encrypt a byte array with a given password.
   *    Salt will be the first 8 bytes of the byte array returned.
   */
  private static byte[] passwordEncrypt(char[] password, byte[] plaintext) throws Exception {

    // Create the salt.
    byte[] salt = new byte[8];
    Random random = new Random();
    random.nextBytes(salt);

    // Create a PBE key and cipher.
    PBEKeySpec keySpec = new PBEKeySpec(password);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
    SecretKey key = keyFactory.generateSecret(keySpec);
    PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
    Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
    cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);

    // Encrypt the array
    byte[] ciphertext = cipher.doFinal(plaintext);

    // Write out the salt, then the ciphertext and return it.
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    baos.write(salt);
    baos.write(ciphertext);
    return baos.toByteArray();
  }

  /**
   *    Utility method to decrypt a byte array with a given password.
   *    Salt will be the first 8 bytes in the array passed in.
   */
  private static byte[] passwordDecrypt(char[] password, byte[] ciphertext) throws Exception {

    // Read in the salt.
    byte[] salt = new byte[8];
    ByteArrayInputStream bais = new ByteArrayInputStream(ciphertext);
    bais.read(salt,0,8);

    // The remaining bytes are the actual ciphertext.
    byte[] remainingCiphertext = new byte[ciphertext.length-8];
    bais.read(remainingCiphertext,0,ciphertext.length-8);

    // Create a PBE cipher to decrypt the byte array.
    PBEKeySpec keySpec = new PBEKeySpec(password);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
    SecretKey key = keyFactory.generateSecret(keySpec);
    PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
    Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");

    // Perform the actual decryption.
    cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
    return cipher.doFinal(remainingCiphertext);
  }
}

编辑:

您需要将 JVM 的 Java 策略更改为 Java 加密扩展 (JCE) 无限强度管辖权才能使用此代码。有关 JAVA 政策变更的所有相关信息,请点击此处


推荐