从String文件创建javaPrivateKey和PublicKey



你好,

还有另一个第三方需要我的web应用程序以加密格式向他们发送一些数据。因此,他们给我发了一些这样做的指南,然而,我不熟悉它,我试图四处搜索,但看起来我是谷歌错误的方式。

指南如下:

  1. 运行openssl命令生成私钥:

    openssl ecparam-name prime256v1-genkey-out myprivate.pem

运行此命令后,我输出了一个priva.pem文件,我看到里面有一些密钥以"=="结尾,如下所示:

-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILefWfeuZOgnbDlxpwo3uQ2xQXfhXHUPTS+vKzvVZdCToAoGCCqGSM49
AwEHoUQDQgAE4MeQspGRJ1qdpweBfiaT5P84alZdga1f7mSpa5HqXTH58u0ZWJUQ
J7ToU/bUOPITh4FX07AV6wrgFCmwtUenDQ==
-----END EC PRIVATE KEY-----
  1. 第二个是运行openssl命令生成公钥,然后发送它们:

    openssl ec-in myprivate.pem-pubout-out mypublic.pem

  2. 将私钥转换为pkcs8格式:

    openssl pkcs8-topk8-nocrypt-in myprivate.pem-out mypkcs8.pem

  3. 第三方会给我一个字符串格式的公钥,然后让我生成一个密钥,并向我提供一些java代码,如下所示:

第一个是生成密钥,第二个是加密:

public static SecretKey generateSharedSecret(PrivateKey privateKey,
PublicKey publicKey) {
try {
KeyAgreement keyAgreement = KeyAgreement.getInstance( "ECDH" );
keyAgreement.init( privateKey );
keyAgreement.doPhase( publicKey, true );
SecretKeySpec key = new SecretKeySpec(
keyAgreement.generateSecret( ), "AES" );
return key;
} catch ( Exception e ) {
// TODO Auto-generated catch block
e.printStackTrace( );
return null;
}
}
public static String encryptString(SecretKey key, String plainText) {
try {
String myIv = "Testing @ IV!";
byte[] iv = myIv.getBytes( "UTF-8" );
IvParameterSpec ivSpec = new IvParameterSpec( iv );
Cipher cipher = Cipher.getInstance( "AES / CBC / PKCS5Padding" );
byte[] plainTextBytes = plainText.getBytes( "UTF-8" );
byte[] cipherText;
cipher.init( Cipher.ENCRYPT_MODE, key, ivSpec );
cipherText = new byte[cipher.getOutputSize( plainTextBytes.length )];
int encryptLength = cipher.update( plainTextBytes, 0,
plainTextBytes.length, cipherText, 0 );
encryptLength += cipher.doFinal( cipherText, encryptLength );
return bytesToHex( cipherText );
} catch ( Exception e ) {
e.printStackTrace( );
return null;
}
}

以及字节到十六进制字符串的方法:

public static String bytesToHex(byte[] byteArray) {
StringBuffer hexStringBuffer = new StringBuffer( );
for ( int i = 0; i < byteArray.length; i++ ) {
hexStringBuffer.append( String.format( "%02X", byteArray[ i ] ) );
}
return hexStringBuffer.toString( );
}

我已经通过使用openssl命令自行生成了一个私钥和一个公钥,但第四步告诉我,他们也会给我一个公钥。因此我不明白,我应该使用哪个公钥。

此外,如何将String转换为javaPrivateKeyPublicKey对象?

*addon*我试图将der文件转换为javaPublicKey对象,它看起来很有效。在此之前,我使用openssl命令将pem转换为der:

openssl pkey -pubin -in ecpubkey.pem -outform der -out ecpubkey.der

这是java代码:

File f = new File("/home/my/Desktop/key/ecpubkey.der");
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
KeyFactory fact = KeyFactory.getInstance("EC");
PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(keyBytes));

然而,当我试图将der文件转换为javaPrivateKey对象时,我遇到了java.security.spec.InvalidKeySpecException: java.io.IOException: insufficient data,我所做的如下:

openssl ecparam -name prime256v1 -genkey -out priv.pem
openssl pkcs8 -topk8 -nocrypt -in priv.pem -outform der -out priv.der

下面是我的java代码:

File f2 = new File("/home/my/Desktop/key/priv.der");
FileInputStream fis2 = new FileInputStream(f2);
DataInputStream dis2 = new DataInputStream(fis2);
byte[] keyBytes2 = new byte[(int) f.length()];
dis2.readFully(keyBytes2);
dis2.close();
KeyFactory fact2 = KeyFactory.getInstance("EC");
PrivateKey pKey = fact2.generatePrivate( new PKCS8EncodedKeySpec(keyBytes2) ); // this line hit insufficient data

Diffie-Hellman在维基百科中得到了很好的解释——可能还有这里数百个Q中的一些,以及加密货币。SX和安全。SX,关于它,但我不容易找到哪个。简而言之:

  • 您生成一个密钥对,保留您的私钥,并将您的公钥提供给另一方

  • 另一方也做同样的事情(或其反射):生成密钥对,保留他们的私钥,并将公钥提供给

  • 您使用您的私钥和他们的公钥来计算"协议"值

  • 它们类似地使用它们的私钥和您的公钥来计算相同的agreement'值。这也被称为共享秘密,因为你和对方都知道,但任何窃听你流量的人都不知道。

大纲中的"提供"省略了许多非常重要的细节。至关重要的是,当你向另一方提供公钥时,他们实际上会得到你的公钥,而不是被对手更改或替换的值。同样,当他们向你提供公钥时也很重要,你得到的是真的,而不是修改或伪造的。这是实际DH系统大多崩溃的地方,而你在这里提到的任何保护或复杂情况都表明,如果你的计划被用于任何值得窃取的事情,它将是不安全的,很容易被破坏。

请注意,你永远不应该向任何人透露或"发送"你的私钥,他们同样也不应该透露自己的私钥。这是公钥(或"非对称")密码学具有任何价值或用途的主要基础。

键的表示方式有很多,但只有一些与您相关。

公共密钥通常在中表示

  • X.509中定义的ASN.1结构SubjectPublicKeyInfo以及PKIX中更方便的结构,主要在rfc5280#4.1和#4.1.2.7以及rfc3279 2.3中,编码在DER中,其具有这样的限制,即在该编码中使用的许多字节不是有效字符,并且不能被正确地显示或以其他方式操纵

  • 同样的ASN.1 DER结构以'PEM'格式'wrapped',它将麻烦的二进制数据以易于操作的形式转换为所有可显示的字符。PEM格式最初是为一种称为"隐私增强邮件"的安全电子邮件方案创建的,该方案已被其他方案和技术取代,但它定义的格式仍在使用。公钥PEM格式最近由rfc7468#13重新标准化(如您所见,参考号为rfc5280)。

OpenSSL同时支持这两种功能,但您主要使用的命令行实用程序默认为PEM,并且由于您需要将密钥传递给"他们",他们需要将密钥传达给您,PEM可能是最可靠和/或最方便的方法

Java直接只支持DER,因此假设您在SPKI PEM中收到他们的公钥,要在Java中使用它,您需要将其转换为DER。您可以在OpenSSL 中执行此操作

openssl pkey -pubin -in theirpub.pem -outform der -out theirpub.der

然后将DER读取到Java加密密钥工厂:

byte[] theirpubder = Files.readAllBytes(Paths.get(whatever));
KeyFactory fact = KeyFactory.getInstance("EC");
PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(theirpubder));
// can downcast to ECPublicKey if you want to be more specific

或者,您可以让Java转换PEM,这并不太难;有几种变体,但我喜欢:

String theirpubpem = new String(Files.readAllBytes(Paths.get(whatever)));
// IN GENERAL letting new String(byte[]) default the charset is dangerous, but PEM is OK
byte[] theirpubder = Base64.getMIMEDecoder().decode(theirpubpem.replaceAll("-----[^\n]*\n","") );
// continue as for DER

对于私钥Java与OpenSSL共享的表示形式要多得多,但只有一种(或两种)。由于您只需要在本地存储私钥,而不需要"发送"私钥,因此可能不需要PEM;如果是这样,您只需将-outform der添加到pkcs8 -topk8 -nocrypt命令中,适当调整名称,并以与上面相同的方式在Java KeyFactory中直接读取结果,除了PKCS8EncodedKeySpecgeneratePrivate[EC]PrivateKey。如果你确实想把它存储在(PKCS8清除)PEM中,你也可以结合以上内容。


直接使用DH协议值作为对称密码(如AES)密钥是非标准的,通常不被认为是良好的做法,尽管对于具有prime256v1(也称为secp256r1或p-256)的ECDH,这在技术上是可能的。AFAIK所有好的标准都使用介于两者之间的密钥推导步骤(也称为密钥推导函数或KDF)。由于你还没有向我们展示他们的"指南",我不能说这是否正确——至少是正确的小值。

可以肯定的是,对于同一个密钥(在本例中是相同的DH结果),多次使用具有固定IV的CBC是不安全的。我认为"测试"意味着你计划用更好的东西来代替它。

此外,仅供参考,您不需要来使用Cipher.init,update,doFinalAPI的全部复杂性。当数据足够小,可以放入内存时,如这里所示,您可以执行以下操作:

cipher.init(ENCRYPT_MODE, key, parms);
byte[] encrypted = cipher.doFinal (plainbytes);
// or since you want to hexify it
... bytesToHex (cipher.doFinal (plainbytes)) ...

最后,因为Javabyte是签名的,所以bytesToHex将输出几乎正好是前缀为FFFFFF的所有字节的一半。这很不寻常,而且非常丑陋,但我不知道这对你来说是否"正确"。

基于dave_thompson_085的解释和代码,我成功地创建了我的java PublicKey和Privatekey,包括以下内容:

public static PublicKey getPublicKey(String filename) throws IOException, GeneralSecurityException {
String publicKeyPEM = getKey(filename);
return getPublicKeyFromString(publicKeyPEM);
}
private static String getKey(String filename) throws IOException {
// Read key from file
String strKeyPEM = "";
BufferedReader br = new BufferedReader(new FileReader(filename));
String line;
while ((line = br.readLine()) != null) {
strKeyPEM += line + "n";
}
br.close();
return strKeyPEM;
}
public static PublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
BASE64Decoder b = new BASE64Decoder();
byte[] encoded = b.decodeBuffer(publicKeyPEM);
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
return pubKey;
}

这是用于私钥的

public static PrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException {
String privateKeyPEM = getKey(filename);
return getPrivateKeyFromString(privateKeyPEM);
}
public static PrivateKey getPrivateKeyFromString(String key) throws IOException, GeneralSecurityException {
String privateKeyPEM = key;
privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----n", "");
privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
BASE64Decoder b = new BASE64Decoder();
byte[] encoded = b.decodeBuffer(privateKeyPEM);
KeyFactory kf = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
PrivateKey privKey = (PrivateKey) kf.generatePrivate(keySpec);
return privKey;
}

非常感谢@dave_thompson_085的解释。

最新更新