Usando una clave privada cifrada y PEM para firmar un mensaje de forma nativa

Estoy tratando de usar un certificado PEM (X.509) (almacenado en un archivo privateKey.pem en el disco) para firmar mensajes enviados a través de sockets en Java, pero estoy teniendo problemas para encontrar un ejemplo que esté cerca. Normalmente soy un chico de C ++ que está interviniendo para ayudar en este proyecto, así que ha sido un poco difícil para mí poner todo junto en un código que funciona cuando no estoy familiarizado con las API.

Desafortunadamente, estoy limitado a los métodos que vienen de forma estándar con Java (1.6.0 Actualización 16), así que aunque encontré un ejemplo similar usando el PEMReader de BouncyCastle , no me ayudó mucho en este proyecto en particular.

Mi clave privateKey.pem está protegida contra contraseña, en la forma de:

-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,63A862F284B1280B [...] tsjQI4H8lhOBuk+NelHu7h2+uqbBQzwkPoA8IqbPXUz+B/MAGhoTGl4AKPjfm9gu OqEorRU2vGiSaUPgDaRhdPKK0stxMxbByUi8xQ2156d/Ipk2IPLSEZDXONrB/4O5 [...] -----END RSA PRIVATE KEY----- 

La clave se generó usando OpenSSL:

 openssl.exe genrsa -out private_key.pem 4096 

No puedo convertir esta clave a DER u otro formato antes del tiempo de ejecución, las conversiones necesarias deberán realizarse internamente en el código, ya que la clave debe ser fácilmente reemplazable y el formato seguirá siendo PEM.

He oído una combinación de cosas de las que no estoy del todo seguro, y esperaba que las mentes colectivas aquí en SO pudieran ayudar a unir las piezas.

He oído decir que el certificado PEM necesita Base64 Decoded para convertirlo en un certificado DER que se puede usar. Tengo una herramienta de deencoding Base64 llamada MiGBase64, pero no estoy del todo seguro de cómo / cuándo debe realizarse esta deencoding.

Me perdí en los documentos de API de Java tratando de rastrear los 15 diferentes tipos de claves, KeyStores, KeyGenerators, Certificados, etc. que existen, pero no estoy lo suficientemente familiarizado con ninguno de ellos para identificar correctamente cuál necesito que sea. usando y cómo usarlos juntos.

El algoritmo básico parece bastante simple, por lo que ha sido particularmente frustrante que no haya podido escribir una implementación igualmente simple:

1) Lee el privateKey.pem del archivo
2) Cargue la clave privada en la clase XXX, usando la frase de contraseña para descifrar la clave
3) Use el objeto clave con la clase Firma para firmar el mensaje

La ayuda con esto, especialmente el código de ejemplo, es muy apreciada. He estado luchando por encontrar ejemplos útiles para este problema, ya que la mayoría de los ejemplos ‘cercanos’ generan claves nuevas, usan BouncyCastle o simplemente usan diferentes formas de claves / clases que no se aplican aquí.

Esto parece un problema realmente simple pero me está volviendo loco, ¿alguna respuesta realmente simple?

Si está utilizando BouncyCastle, intente lo siguiente:

 import java.io.File; import java.io.FileReader; import java.io.IOException; import java.security.KeyPair; import java.security.Security; import java.security.Signature; import java.util.Arrays; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PasswordFinder; import org.bouncycastle.util.encoders.Hex; public class SignatureExample { public static void main(String [] args) throws Exception { Security.addProvider(new BouncyCastleProvider()); String message = "hello world"; File privateKey = new File("private.pem"); KeyPair keyPair = readKeyPair(privateKey, "password".toCharArray()); Signature signature = Signature.getInstance("SHA256WithRSAEncryption"); signature.initSign(keyPair.getPrivate()); signature.update(message.getBytes()); byte [] signatureBytes = signature.sign(); System.out.println(new String(Hex.encode(signatureBytes))); Signature verifier = Signature.getInstance("SHA256WithRSAEncryption"); verifier.initVerify(keyPair.getPublic()); verifier.update(message.getBytes()); if (verifier.verify(signatureBytes)) { System.out.println("Signature is valid"); } else { System.out.println("Signature is invalid"); } } private static KeyPair readKeyPair(File privateKey, char [] keyPassword) throws IOException { FileReader fileReader = new FileReader(privateKey); PEMReader r = new PEMReader(fileReader, new DefaultPasswordFinder(keyPassword)); try { return (KeyPair) r.readObject(); } catch (IOException ex) { throw new IOException("The private key could not be decrypted", ex); } finally { r.close(); fileReader.close(); } } private static class DefaultPasswordFinder implements PasswordFinder { private final char [] password; private DefaultPasswordFinder(char [] password) { this.password = password; } @Override public char[] getPassword() { return Arrays.copyOf(password, password.length); } } } 

El comando OpenSSL genera par de claves y lo codifica en formato PKCS # 1. Si no utiliza el cifrado (no proporcionó la contraseña para el comando), el PEM es simplemente DER codificado en Base64 para PKCS # 1 RSAPrivateKey.

Desafortunadamente, Sun’s JCE no proporciona una interfaz pública para leer la clave en este formato. Tienes 2 opciones,

  1. Importe la clave en el almacén de claves y podrá leerla desde allí. Keytool no permite la importación de claves privadas. Puedes encontrar otras herramientas para hacer esto.

  2. La biblioteca OAuth tiene una función para manejar esto. Mira el código aquí,

http://oauth.googlecode.com/svn/code/java/core/commons/src/main/java/net/oauth/signature/pem/PKCS1EncodedKeySpec.java