如何把RSA公钥的大整数转为OpenSSH那种字符串

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何把RSA公钥的大整数转为OpenSSH那种字符串相关的知识,希望对你有一定的参考价值。

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;

import com.icardpay.tds.stp.fastpay.common.Convertor;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import tangdi.engine.context.Log;

/**
* RSAHelper - 对RSA 签名&验签/分段加密&分段解密 的包装 签名算法: "SHA1withRSA", 私钥进行签名; 公钥进行验签.
* 加密算法: "RSA/ECB/PKCS1Padding", 公钥进行加密; 私钥进行解密.
*
* [localPrivKey]是自己的私钥, 自己的公钥给通信对方. [peerPubKey]是对方的公钥, 对方的私钥在对方那边. 为了方便,
* 这里假定双方的密钥长度一致, 签名和加密的规则也一致.
*
* 以`Base64Str`结尾的参数表示内容是Base64编码的字符串, 其他情况都是raw字符串.
*/

public class RSAHelper

private static RSAHelper instance = null;

public static final String KEY_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
public static final int KEYBIT = 2048;
public static final int RESERVEBYTES = 11;

private static PrivateKey localPrivKey;
private static PublicKey peerPubKey;

private RSAHelper()


public static RSAHelper getInstance()
if(instance == null)
//双重检查加锁,只有在第一次实例化时,才启用同步机制,提高了性能。
synchronized(RSAHelper.class)
if(instance == null)
String localPrivKeyBase64Str = CmbcProps.getInstance().getConfigItem("OTHERLOCALPRIVKEY", "");
String peerPubKeyBase64Str = CmbcProps.getInstance().getConfigItem("OTHERPEERPUBKEY", "");

try
/**
* 初始化自己的私钥,对方的公钥以及密钥长度.
* localPrivKeyBase64Str
* Base64编码的私钥,PKCS#8编码. (去掉pem文件中的头尾标识)
* peerPubKeyBase64Str
* Base64编码的公钥. (去掉pem文件中的头尾标识)
*/
BASE64Decoder base64Decoder = new BASE64Decoder();

byte[] buffer1 = base64Decoder.decodeBuffer(localPrivKeyBase64Str);
PKCS8EncodedKeySpec keySpec1 = new PKCS8EncodedKeySpec(buffer1);
KeyFactory keyFactory1 = KeyFactory.getInstance(KEY_ALGORITHM);
localPrivKey = keyFactory1.generatePrivate(keySpec1);

byte[] buffer2 = base64Decoder.decodeBuffer(peerPubKeyBase64Str);
KeyFactory keyFactory2 = KeyFactory.getInstance(KEY_ALGORITHM);
X509EncodedKeySpec keySpec2 = new X509EncodedKeySpec(buffer2);
peerPubKey = keyFactory2.generatePublic(keySpec2);

/**
* 实例化对象
*/
instance = new RSAHelper();
catch (NoSuchAlgorithmException e)
Log.info("无此算法");
catch (InvalidKeySpecException e)
Log.info("公钥非法");
catch (IOException e)
Log.info("公钥数据内容读取错误");
catch (NullPointerException e)
Log.info("公钥数据为空");
catch (Exception e)
Log.error(e, "初始化密钥失败");




return instance;


/**
* RAS加密
*
* @param plainBytes
* 待加密信息
* @return byte[]
* @throws Exception
*/
public byte[] encryptRSA(byte[] plainBytes, boolean useBase64Code, String charset)
throws Exception
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
int decryptBlock = KEYBIT / 8; // 256 bytes
int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
// 计算分段加密的block数 (向上取整)
int nBlock = (plainBytes.length / encryptBlock);
if ((plainBytes.length % encryptBlock) != 0) // 余数非0,block数再加1
nBlock += 1;

// 输出buffer, 大小为nBlock个decryptBlock
ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock
* decryptBlock);
cipher.init(Cipher.ENCRYPT_MODE, peerPubKey);
// cryptedBase64Str =
// Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes()));
// 分段加密
for (int offset = 0; offset < plainBytes.length; offset += encryptBlock)
// block大小: encryptBlock 或 剩余字节数
int inputLen = (plainBytes.length - offset);
if (inputLen > encryptBlock)
inputLen = encryptBlock;

// 得到分段加密结果
byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
// 追加结果到输出buffer中
outbuf.write(encryptedBlock);

// 如果是Base64编码,则返回Base64编码后的数组
if (useBase64Code)
return encodeBase64String(outbuf.toByteArray()).getBytes(
charset);
else
return outbuf.toByteArray(); // ciphertext



/**
* RSA解密
*
* @param cryptedBytes
* 待解密信息
* @return byte[]
* @throws Exception
*/
public byte[] decryptRSA(byte[] cryptedBytes, boolean useBase64Code,
String charset) throws Exception
byte[] data;

// 如果是Base64编码的话,则要Base64解码
if (useBase64Code)
data = decodeBase64(new String(cryptedBytes, charset));
else
data = cryptedBytes;


Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
int decryptBlock = KEYBIT / 8; // 256 bytes
int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
// 计算分段解密的block数 (理论上应该能整除)
int nBlock = (data.length / decryptBlock);
// 输出buffer, , 大小为nBlock个encryptBlock
ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock
* encryptBlock);
cipher.init(Cipher.DECRYPT_MODE, localPrivKey);
// plaintext = new
// String(cipher.doFinal(Base64.decodeBase64(cryptedBase64Str)));
// 分段解密
for (int offset = 0; offset < data.length; offset += decryptBlock)
// block大小: decryptBlock 或 剩余字节数
int inputLen = (data.length - offset);
if (inputLen > decryptBlock)
inputLen = decryptBlock;


// 得到分段解密结果
byte[] decryptedBlock = cipher.doFinal(data, offset, inputLen);
// 追加结果到输出buffer中
outbuf.write(decryptedBlock);

outbuf.flush();
outbuf.close();
return outbuf.toByteArray();


/**
* RSA签名
*
* @param plainBytes
* 需要签名的信息
* @return byte[]
* @throws Exception
*/
public byte[] signRSA(byte[] plainBytes, boolean useBase64Code,
String charset) throws Exception
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(localPrivKey);
signature.update(plainBytes);

// 如果是Base64编码的话,需要对签名后的数组以Base64编码
if (useBase64Code)
return encodeBase64String(signature.sign())
.getBytes(charset);
else
return signature.sign();



/**
* 验签操作
*
* @param plainBytes
* 需要验签的信息
* @param signBytes
* 签名信息
* @return boolean
*/
public boolean verifyRSA(byte[] plainBytes, byte[] signBytes,
boolean useBase64Code, String charset) throws Exception
boolean isValid;
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(peerPubKey);
signature.update(plainBytes);

// 如果是Base64编码的话,需要对验签的数组以Base64解码
if (useBase64Code)
isValid = signature.verify(decodeBase64(new String(
signBytes, charset)));
else
isValid = signature.verify(signBytes);

return isValid;


private String encodeBase64String(byte[] dataBytes)
return Base64.encodeBase64(dataBytes) == null ? null : new String(Base64.encodeBase64(dataBytes), Charset.forName("UTF-8"));


private byte[] decodeBase64(String base64String)
return Base64.decodeBase64(base64String == null ? null : base64String.getBytes(Charset.forName("UTF-8")));


public static void main(String[] args) throws Exception

String plaintext = "你好,测试";

System.out.println("=====> init <=====");
RSAHelper cipher = RSAHelper.getInstance();

System.out.println("=====> sign & verify <=====");

// 签名
byte[] signBytes = cipher.signRSA(plaintext.getBytes("UTF-8"), false,
"UTF-8");

// 验证签名
boolean isValid = cipher.verifyRSA(plaintext.getBytes("UTF-8"),
signBytes, false, "UTF-8");
System.out.println("isValid: " + isValid);

// 加密和解密
System.out.println("=====> encrypt & decrypt <=====");
// 对明文加密
byte[] cryptedBytes = cipher.encryptRSA(plaintext.getBytes("UTF-8"),
false, "UTF-8");

System.out.println(Convertor.byte2HexString(cryptedBytes));

// 对密文解密
byte[] decryptedBytes = cipher.decryptRSA(cryptedBytes, false, "UTF-8");
System.out.println("decrypted: " + new String(decryptedBytes, "UTF-8"));

参考技术A 感觉是进制的问题。。
你把modulus: 138366379149556651529264271167627776457858687036310253186931869304419603614951358029547428616757746198297697875913048009038184919740987891480058872914394404915587279803233281027437510321771042949607120103195234015711071571988324336148543386523834345645243818986713709872730288332630296741272814978967784227069
变成16进制显示就可以了 应该

Node.js:如何将 RSA 公钥转换为 OpenSSH 格式?

【中文标题】Node.js:如何将 RSA 公钥转换为 OpenSSH 格式?【英文标题】:Node.js: How to convert RSA public key to OpenSSH format? 【发布时间】:2019-05-22 16:02:24 【问题描述】:

我使用的是节点版本:v10.14.1,我使用以下代码生成密钥对:

generateKeyPair('rsa', 
    modulusLength: 4096,
    publicKeyEncoding: 
        type: 'pkcs1',
        format: 'pem'
    ,
    privateKeyEncoding: 
        type: 'pkcs8',
        format: 'pem',
        cipher: 'aes-256-cbc',
        passphrase: ''
    
, (err, publicKey, privateKey) => 
  // Do stuff
);

这将创建一个这种格式的公钥:

-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----

不幸的是,有时需要不同的格式。在我的情况下,将公钥上传到 AWS 需要 OpenSSH 格式,我认为是这样的:

ssh-rsa 
...

如何将 RSA 公钥格式转换为 OpenSSH 格式或直接使用 generateKeyPair() 生成?

【问题讨论】:

【参考方案1】:

node-sshpk 软件包可能会帮助您: https://github.com/joyent/node-sshpk

您可以使用pubKey.toBuffer(),或者更复杂一点的pubKey.toBuffer('ssh')。或者pubKey.toString('ssh'),如果你需要它作为一个字符串。

在您的示例中,代码应该是这样的:

const  generateKeyPair    = require('crypto');
const sshpk                 = require('sshpk');

generateKeyPair('rsa', 
    modulusLength: 4096,
    publicKeyEncoding: 
        type: 'pkcs1',
        format: 'pem'
    ,
    privateKeyEncoding: 
        type: 'pkcs8',
        format: 'pem',
    
, (err, publicKey, privateKey) => 
    if(err)
        // handle Error
    
    else
        const pemKey = sshpk.parseKey(publicKey, 'pem');
        const sshRsa = pemKey.toString('ssh');
        console.log(ssh_rsa_2);
    
);

【讨论】:

很抱歉,您的回答花了这么长时间才接受,尽管它帮助(并且仍然)解决了我的问题。

以上是关于如何把RSA公钥的大整数转为OpenSSH那种字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何把OpenSSH公钥转换成OpenSSL格式

在 AWS 上导入密钥对之前,如何通过 Java 验证 OpenSSH 公钥格式?

如何配置openSSH

Linux 124课程 9配置和安全OpenSSH服务

RSA公钥只返回一串base64字符 怎么使用

openssh 免用户名/密码/服务器地址,登录远程服务器