如何使用 IAIK JCE 在 Java 中使用 PKCS#5 格式的 PBE 加密 RSA 私钥?

2024-01-07

我已经创建了 RSA 密钥对。现在,我尝试使用 DES 算法加密私钥,将其格式化为 PKCS#5 并将其打印在控制台上。不幸的是,生成的私钥不起作用。当我尝试使用它时,输入后right密码短语,ssh 客户端返回密码短语无效:

加载密钥“test.key”:提供的用于解密私钥的密码不正确

有人可以告诉我我错在哪里吗?

这是代码:

private byte[] iv;

public void generate() throws Exception {
    RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
    generator.initialize(2048);
    KeyPair keyPair = generator.generateKeyPair();

    String passphrase = "passphrase";
    byte[] encryptedData = encrypt(keyPair.getPrivate().getEncoded(), passphrase);
    System.out.println(getPrivateKeyPem(Base64.encodeBase64String(encryptedData)));
}

private byte[] encrypt(byte[] data, String passphrase) throws Exception {
    String algorithm = "PBEWithMD5AndDES";
    salt = new byte[8];
    int iterations = 1024;

    // Create a key from the supplied passphrase.
    KeySpec ks = new PBEKeySpec(passphrase.toCharArray());
    SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
    SecretKey key = skf.generateSecret(ks);

    // Create the salt from eight bytes of the digest of P || M.
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update(passphrase.getBytes());
    md.update(data);
    byte[] digest = md.digest();
    System.arraycopy(digest, 0, salt, 0, 8);
    AlgorithmParameterSpec aps = new PBEParameterSpec(salt, iterations);

    Cipher cipher = Cipher.getInstance(AlgorithmID.pbeWithSHAAnd3_KeyTripleDES_CBC.getJcaStandardName());
    cipher.init(Cipher.ENCRYPT_MODE, key, aps);
    iv = cipher.getIV();
    byte[] output = cipher.doFinal(data);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    out.write(salt);
    out.write(output);
    out.close();
    return out.toByteArray();
}

private String getPrivateKeyPem(String privateKey) throws Exception {
    StringBuffer formatted = new StringBuffer();
    formatted.append("-----BEGIN RSA PRIVATE KEY----- " + LINE_SEPARATOR);

    formatted.append("Proc-Type: 4,ENCRYPTED" + LINE_SEPARATOR);
    formatted.append("DEK-Info: DES-EDE3-CBC,");
    formatted.append(bytesToHex(iv));

    formatted.append(LINE_SEPARATOR);
    formatted.append(LINE_SEPARATOR);

    Arrays.stream(privateKey.split("(?<=\\G.{64})")).forEach(line -> formatted.append(line + LINE_SEPARATOR));
    formatted.append("-----END RSA PRIVATE KEY-----");

    return formatted.toString();
}

private String bytesToHex(byte[] bytes) {
    char[] hexArray = "0123456789ABCDEF".toCharArray();
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j < bytes.length; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}

这是生成的 PKCS#5 PEM 格式的私钥:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,CA138D5D3C048EBD

+aZNZJKLvNtlmnkg+rFK6NFm45pQJNnJB9ddQ3Rc5Ak0C/Igm9EqHoOS+iy+PPjx
pEKbhc4Qe3U0GOT9L5oN7iaWL82gUznRLRyUXtOrGcpE7TyrE+rydD9BsslJPCe+
y7a9LnSNZuJpJPnJCeKwzy5FGVv2KmDzGTcs9IqCMKgV69qf83pOJU6Dk+bvh9YP
3I05FHeaQYQk8c3t3onfljVIaYOfbNYFLZgNgGtPzFD4OpuDypei/61i3DeXyFUA
SNSY5fPwp6iSeSKtwduSEJMX31TKSpqWeZmEmMNcnh8oZz2E0jRWkbkaFuZfNtqt
aVpLN49oRpbsij+i1+udyuIXdBGRYt9iDZKnw+LDjC3X9R2ceq4AOdfsmEVYbO1i
YNms9eXSkANuchiI2YqkKsCwqI5S8S/2Xj76zf+pCDhCTYGV3RygkN6imX/Qg2eF
LOricZZTF/YPcKnggqNrZy4KSUzAgZ9NhzWCWOCiGFcQLYIo+qDoJ8t4FwxQYhx9
7ckzXML0n0q5ba5pGekLbBUJ9/TdtnqfqmYrHX+4OlrR7XAu478v2QH6/QtNKdZf
VRTqmKKH0n8JL9AgaXWipQstW5ERNZJ9YPBASQzewVNLv4gRZRTw8bYcU/hiPbWp
eqULYYI9324RzY3UTsz3N9X+zQsT02zNdxud7XmmoHL493yyvqT9ERmF4uckGYei
HZ16KFeKQXE9z+x0WNFAKX3nbttVlN5O7TAmUolFTwu11UDsJEjrYMZRwjheAZyD
UnV1LwhFT+QA0r68Mto3poxpAawCJqPP50V4jbhsOb0J7sxT8fo2mBVSxTdb9+t1
lG++x/gHcK51ApK1tF1FhRRKdtOzSib376Kmt23q0jVDNVyy09ys+8LRElOAY1Es
LIuMMM3F7l+F4+knKh3/IkPZwRIz3f9fpsVYIePPS1bUdagzNoMqUkTwzmq6vmUP
C5QvN6Z5ukVCObK+T8C4rya8KQ/2kwoSCRDIX6Mzpnqx6SoO4mvtBHvPcICGdOD6
aX/SbLd9J2lenTxnaAvxWW0jkF6q9x9AAIDdXTd9B5LnOG0Nq+zI+6THL+YpBCB9
6oMO4YChFNoEx0HZVdOc8E7xvXU2NqinmRnyh7hCR5KNfzsNdxg1d8ly67gdZQ1Q
bk1HPKvr6T568Ztapz1J/O6YWRIHdrGyA6liOKdArhhSI9xdk3H3JFNiuH+qkSCB
0mBYdS0BVRVdKbKcrk4WRHZxHsDsQn1/bPxok4dCG/dGO/gT0QlxV+hOV8h/4dJO
mcUvzdW4I8XKrX5KlTGNusVRiFX3Cy8FFZQtSxdWzr6XR6u0bUKS+KjDl1KoFxPH
GwYSTkJVE+fbjsSisQwXjWnwGGkNDuQ1IIMJOAHMK4Mly1jMdFF938WNY7NS4bIb
IXXkRdwxhdkRDiENSMXY8YeCNBJMjqdXZtR4cwGEXO+G+fpT5+ZrfPbQYO+0E0r4
wGPKlrpeeR74ALiaUemUYVIdw0ezlGvdhul2KZx4L82NpI6/JQ7shq9/BEW2dWhN
aDuWri2obsNL3kk2VBWPNiE6Rn/HtjwKn7ioWZ3IIgOgyavcITPBe0FAjxmfRs5w
VWLFBXqcyV9cu1xS4GoCNLk0MrVziUCwHmwkLIzQZos=
-----END RSA PRIVATE KEY-----

提前致谢。


不存在 PKCS#5 格式之类的东西。 PKCS#5 主要定义了两个基于密码的密钥派生函数和使用它们的基于密码的加密方案,以及基于密码的 MAC 方案,但没有定义任何数据格式。 (它确实为这些操作定义了 ASN.1 OID,并为其定义了 ASN.1 结构参数-- 主要是 PBKDF2 和 PBES2,因为 PBKDF1 和 PBES1 的唯一参数是盐。) PKCS#5 还为 CBC 模式数据加密定义了填充方案;此填充由 PKCS#7 稍微增强,并被许多其他应用程序使用,通常将其称为 PKCS5 填充或 PKCS7 填充。这些都不是数据格式,也不涉及 RSA(或其他)私钥本身。

您显然想要的文件格式是 OpenSSH 使用的文件格式(很长一段时间以来一直如此,然后在过去几年中作为默认格式,直到一个月前的 OpenSSH 7.8 将其变为可选),因此也被其他软件使用希望与 OpenSSH 兼容甚至互换。这种格式实际上是由 OpenSSL 定义的,OpenSSH 长期以来一直将其用于大部分加密技术。 (继 Heartbleed 之后,OpenSSH 创建了一个名为 LibreSSL 的 OpenSSL 分支,它试图在内部变得更加健壮和安全,但有意保持相同的外部接口和格式,并且无论如何都没有被广泛采用。)

It is OpenSSL 定义的几种“PEM”格式之一,并且主要在许多“PEM”例程的手册页上进行了描述,包括PEM_write[_bio]_RSAPrivateKey-- 在您的系统上(如果您有 OpenSSL 并且它不是 Windows),或者在网上 https://www.openssl.org/docs/man1.1.1/man3/PEM_write_RSAPrivateKey.html加密部分接近“PEM 加密格式”部分的末尾,并且它引用的 EVP_BytesToKey 例程类似它自己的手册页 https://www.openssl.org/docs/man1.1.1/man3/EVP_BytesToKey.html。简而言之: 它不使用 pbeSHAwith3_keyTripleDES-CBC (即 SHA1)定义的方案PKCS#12/rfc7292 https://www.rfc-editor.org/rfc/rfc7292#appendix-C orpbeMD5withDES-CBC 方案定义为PBES1 中的 PKCS#5/rfc2898 https://www.rfc-editor.org/rfc/rfc2898#appendix-A.3。相反,它使用EVP_BytesToKey(这是partly基于 PBKDF1),使用 md5 和 1 次迭代,salt 等于 IV,以导出密钥,然后使用任何支持的使用 IV(因此不是流或 ECB)但通常默认为 DES 的对称密码模式进行加密/解密EDE3(又名 3key-TripleDES)CBC 如您所要求。是的,niter=1 的 EVP_BytesToKey 是一个很差的 PBKDF,并且会使这些文件不安全,除非您使用非常强的密码;已经有很多关于这个的问题了。

最后是纯文本该文件格式不是返回的 PKCS#8(通用)编码[RSA]PrivateKey.getEncoded() https://docs.oracle.com/javase/8/docs/api/java/security/Key.html#getFormat--而是仅由 RSA 定义的格式PKCS#1/rfc8017 等 https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1.2。 Proc-type 和 DEK-info 标头与 base64 之间需要空行,并且可能需要破折号-END 行上的行终止符,具体取决于读取的软件。

最简单的方法是使用已经与 OpenSSL 私钥 PEM 格式兼容的软件,包括 OpenSSL 本身。 Java可以运行外部程序:OpenSSH的ssh-keygen如果你有的话,或者openssl genrsa如果你有的话。 BouncyCastle bcpkix 库支持此格式和其他 OpenSSL PEM 格式。如果“ssh 客户端”是 jsch,那么normally读取多种格式的密钥文件,包括这种格式,但是com.jcraft.jsch.KeyPairRSA实际上也支持生成密钥并以这种 PEM 格式写入。 Puttygen 也支持这种格式,但它可以转换的其他格式对 Java 不友好。我确信还有更多。

但如果您需要在自己的代码中执行此操作,请按以下方法操作:

    // given [RSA]PrivateKey privkey, get the PKCS1 part from the PKCS8 encoding
    byte[] pk8 = privkey.getEncoded();
    // this is wrong for RSA<=512 but those are totally insecure anyway
    if( pk8[0]!=0x30 || pk8[1]!=(byte)0x82 ) throw new Exception();
    if( 4 + (pk8[2]<<8 | (pk8[3]&0xFF)) != pk8.length ) throw new Exception();
    if( pk8[4]!=2 || pk8[5]!=1 || pk8[6]!= 0 ) throw new Exception();
    if( pk8[7] != 0x30 || pk8[8]==0 || pk8[8]>127 ) throw new Exception();
    // could also check contents of the AlgId but that's more work
    int i = 4 + 3 + 2 + pk8[8];
    if( i + 4 > pk8.length || pk8[i]!=4 || pk8[i+1]!=(byte)0x82 ) throw new Exception();
    byte[] old = Arrays.copyOfRange (pk8, i+4, pk8.length);
    
    // OpenSSL-Legacy PEM encryption = 3keytdes-cbc using random iv 
    // key from EVP_BytesToKey(3keytdes.keylen=24,hash=md5,salt=iv,,iter=1,outkey,notiv)
    byte[] passphrase = "passphrase".getBytes(); // charset doesn't matter for test value
    byte[] iv = new byte[8]; new SecureRandom().nextBytes(iv); // maybe SIV instead?
    MessageDigest pbh = MessageDigest.getInstance("MD5");
    byte[] derive = new byte[32]; // round up to multiple of pbh.getDigestLength()=16
    for(int off = 0; off < derive.length; off += 16 ){
        if( off>0 ) pbh.update(derive,off-16,16);
        pbh.update(passphrase); pbh.update(iv); 
        pbh.digest(derive, off,  16);
    }
    Cipher pbc = Cipher.getInstance("DESede/CBC/PKCS5Padding");
    pbc.init (Cipher.ENCRYPT_MODE, new SecretKeySpec(derive,0,24,"DESede"), new IvParameterSpec(iv));
    byte[] enc = pbc.doFinal(old);
    
    // write to PEM format (substitute other file if desired)
    System.out.println ("-----BEGIN RSA PRIVATE KEY-----");
    System.out.println ("Proc-Type: 4,ENCRYPTED");
    System.out.println ("DEK-Info: DES-EDE3-CBC," + DatatypeConverter.printHexBinary(iv));
    System.out.println (); // empty line
    String b64 = Base64.getEncoder().encodeToString(enc);
    for( int off = 0; off < b64.length(); off += 64 )
        System.out.println (b64.substring(off, off+64<b64.length()?off+64:b64.length()));
    System.out.println ("-----END RSA PRIVATE KEY-----");

最后,OpenSSL 格式要求加密 IV 和 PBKDF 盐相同,并且它使该值随机,所以我也这样做了。仅用于盐的计算值 MD5(password||data) 隐约类似于现在被接受用于加密的合成 IV (SIV) 结构,但它不一样,而且我不知道是否相同任何有能力的分析师都考虑过 SIV 的情况also用于 PBKDF 盐,所以我不愿意在这里依赖这种技术。如果你想问这一点,它并不是一个真正的编程问题,更适合 cryptography.SX 或者 security.SX。


添加评论:

该代码的输出对我来说适用于 0.70 版本的 puttygen,无论是在 Windows(来自上游=chiark)还是在 CentOS6(来自 EPEL)上。根据来源,仅当 cmdgen 在 sshpubk.c 中调用 key_type 时,才会出现您给出的错误消息,该密钥将第一行识别为以“-----BEGIN”开头,但不是“-----BEGIN OPENSSH PRIVATE KEY” (这是一种非常不同的格式),然后通过 import_ssh2 和 openssh_pem_read 在 import.c 中调用 load_openssh_pem_key ,它找不到以“-----BEGIN”开头并以“PRIVATE KEY-----”结尾的第一行。这很奇怪,因为中间的两个加上“RSA”都是由我的代码生成的andOpenSSH(或 openssl)需要接受它。尝试至少查看第一行(也许是前两行)的每个字节,例如cat -vet or sed -n l或在紧要关头od -c.

RFC 2898 现在已经相当老了;今天的良好实践通常是数十次数千次到数百次数千次迭代,更好的实践是根本不使用迭代哈希,而是使用像 scrypt 或 Argon2 这样的内存困难的东西。但正如我已经写过的,OpenSSL 遗留 PEM 加密是在 20 世纪 90 年代设计的,使用 ONE (un, eine, 1) 迭代,因此是一种较差且不安全的方案。现在没有人可以改变它,因为它就是这样设计的。如果您想要像样的 PBE,请不要使用此格式。

如果您只需要 SSH 的密钥:OpenSSH(已经好几年了)支持,最新版本的 Putty(gen) 可以导入 OpenSSH 定义的“新格式”,它使用 bcrypt,但 jsch 不能。 OpenSSH(使用 OpenSSL)还可以读取(PEM)PKCS8,它允许 PBKDF2(更好,但不是最好)根据需要进行迭代,看起来 jsch 可以,但 Putty(gen) 不行。我不知道 Cyber​​duck 或其他实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 IAIK JCE 在 Java 中使用 PKCS#5 格式的 PBE 加密 RSA 私钥? 的相关文章

随机推荐

  • 我可以调用 jdbc 中的存储过程来使用 mysql 返回表吗?

    我是使用 jdbc 执行 mysql 存储过程的新手 我的问题是 是否可以调用返回 jdbc 中的表的存储过程 我搜索了很多 我知道我可以使用返回一定数量的参数registerOutParameter使用过程 但是包含几行的整个表怎么样 我
  • Facebook 应用程序如何向用户的所有好友发送消息?

    我们正在尝试构建一个应用程序 该应用程序可以根据登录用户的请求并在应用程序中进行适当的祝福 向任何或所有用户的 FB 好友发送一条消息 声明他们已收到一份礼物 我们已经能够以墙贴的形式向少数朋友发送此消息 通知和消息在 API 中不可用 然
  • 如何编写像 init.d 中使用的那样的 bash 脚本?

    我必须编写一个 bash 脚本来制作很多东西 我想像初始化脚本一样打印消息 例如 Doing A OK Doing B ERROR 你知道有什么方法可以做到这一点吗 提前致谢 在我所有的 Linux 机器上 执行此操作的代码位于文件中 et
  • 如何将依赖项注入 iOS 视图控制器?

    我的视图控制器需要向几个模型对象发送消息 如何获取视图控制器内这些模型对象的引用 这些模型对象是 单例 因为系统中一次只能有它们的一份副本 并且它们由多个视图控制器使用 所以我无法在每个视图控制器的 init 方法中实例化它们 我无法使用构
  • 如何在vb.net中检索mysql数据?

    我试图检索具有特定列的 mysql 数据并显示到 vb net 中的文本框 我应该怎么做才能取回它 Dim connect As New MySqlConnection server localhost user id root passw
  • 如何测试父元素对组件绑定的更改?

    我有一个如下所示的组件 想测试一下 onChange方法在绑定的情况下执行myBinding变化 我尝试了一整个早上 但找不到解决这个问题的方法 angular module project myComponent component my
  • 如何成功导入pygame.locals

    你好 Stackoverflowers 我正在学习如何使用 Pygame 进行编程 并且正在尝试导入 pygame locals 根据我正在学习的教程 我是这样开始的 import pygame sys from pygame locals
  • VS 2017 构建工具失败,错误 MSB4019:找不到导入的项目“D:\Microsoft.Cpp.Default.props”

    我正在构建一个新的 TFS 构建服务器 并决定使用 VS 2017 构建工具 而不是安装完整版本的 VS 当我尝试构建 C 项目时 它抛出以下错误 Error MSB4019 The imported project D Microsoft
  • 如何在javascript中清除localstorage、sessionStorage和cookie?然后检索?

    如何彻底清除localstorage sessionStorage and cookies在 JavaScript 中 有什么办法可以在清除这些值后恢复它们吗 如何彻底清除本地存储 localStorage clear 如何彻底清除sess
  • 如何使 navigator.vibrate 在页面加载时在 Onload 上工作

    我想让手机 设备在页面加载时振动 现在只有当我们点击振动按钮时振动才起作用 But the onload标记它不工作 给我一个解决方案来解决这个问题 我什至厌倦了添加window onload example1 也 但仍然不起作用
  • django 将模型实例转换为字典

    我是 Django 的初学者 我需要将 Model 实例转换为类似于 Model objects values 的字典 并带有关系字段 所以我写了一个小函数来做到这一点 def get proper instance field if fi
  • 动态加载 JavaScript 文件

    如何可靠且动态地加载 JavaScript 文件 这可用于实现一个模块或组件 当 初始化 时 该组件将根据需要动态加载所有需要的 JavaScript 库脚本 使用该组件的客户端不需要加载所有库脚本文件 并手动插入
  • Node.js、Express.js - 意外标记 {

    我的应用程序每次到达此行时都会崩溃 const name price req query 似乎无法找到确切的答案 这是错误日志 SyntaxError Unexpected token at exports runInThisContext
  • jsPDF 分页符

    我的页面 tab1 和 tab2 中有 2 个 div 我想将两个 div 导出到 1 个 PDF 文件中 其中 tab1 作为第一页 tab2 从下一页开始 目前 第二个 div 在导出的文件中出现损坏 因此我希望该 div 从下一页出现
  • 如何使用specs2对测试进行分组?

    我习惯了 JUnit 在 JUnit 中 只需在单个文件 类 中定义这些测试并用 Test 然后 为了运行其中几个测试 TestSuite是用创建的 Suite SuiteClasses等等 在specs2中 可以将多个测试分组在两个不同的
  • iOS – UIAppearance外观WhenContainedIn问题

    我正在为导航栏设置图像 如下所示 UINavigationBar appearance setBackgroundImage UIImage imageNamed navbar png forBarMetrics UIBarMetricsD
  • Gmail API users.watch - 没有历史记录的详细信息

    我已成功设置 Google Pub Sub 以使用 Gmail API Watch 功能 如下所述 https developers google com gmail api guides push https developers goo
  • crt1.o:在函数 `_start' 中: - Linux 中对 `main' 的未定义引用

    我正在将应用程序从 Solaris 移植到 Linux 链接的目标文件没有定义 main 但编译和链接在 Solaris 中正确完成 并且生成了可执行文件 在 Linux 中我收到此错误 usr lib gcc x86 64 redhat
  • 同步调用异步 Javascript 函数

    首先 这是一种非常具体的情况 故意以错误的方式将异步调用改造为非常同步的代码库 该代码库有数千行长 而且时间目前没有能力进行更改以 执行对的 它伤害了我的每一根神经 但现实和理想常常不一致 我知道这很糟糕 好吧 顺便说一句 我该如何做到这一
  • 如何使用 IAIK JCE 在 Java 中使用 PKCS#5 格式的 PBE 加密 RSA 私钥?

    我已经创建了 RSA 密钥对 现在 我尝试使用 DES 算法加密私钥 将其格式化为 PKCS 5 并将其打印在控制台上 不幸的是 生成的私钥不起作用 当我尝试使用它时 输入后right密码短语 ssh 客户端返回密码短语无效 加载密钥 te