【问题标题】:Java ssh-rsa string to public keyJava ssh-rsa 字符串到公钥
【发布时间】:2018-05-28 18:06:32
【问题描述】:

我想获取.pub 文件内容的公钥。这是.pub 文件内容的示例(使用ssh-keygen 生成):

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDBPL2s+25Ank3zS6iHUoVk0tS63dZM0LzAaniiDon0tdWwq4vcL4+fV8BsAEcpMeijS92JhDDc9FccXlHbdDcmd6c4ITOt9h9xxhIefGsi1FTVJ/EjVtbqF5m0bu7ruIMGvuP1p5s004roHx9y0UdHvD/yNWLISMhy4nio6jLailIj3FS53Emj1WRNsOrpja3LzPXzhuuj6YnD9yfByT7iGZipxkmleaXrknChPClLI9uhcqtAzBLdd0NVTJLOt/3+d1cSNwdBw9e53wJvpEmH+P8UOZd+oV/y7cHIej4jQpBXVvpJR1Yaluh5RuxY90B0hSescUAj4g/3HVPpR/gE7op6i9Ab//0iXF15uWGlGzipI4lA2/wYEtv8swTjmdCTMNcTDw/1huTDEzZjghIKVpskHde/Lj416c7eSByLqsMg2OhlZGChKznpIjhuNRXz93DwqKuIKvJKSnhqaJDxmDGfG7nlQ/eTwGeAZ6VR50yMPiRTIpuYd767+Nsg486z7p0pnKoBlL6ffTbfeolUX2b6Nb9ZIOxJdpCSNTQRKQ50p4Y3S580cUM1Y2EfjlfIQG1JdmTQYB75AZXi/cB2PvScmF0bXRoj7iHg4lCnSUvRprWA0xbwzCW/wjNqw6MyRX42FFlvSRrmfaxGZxKYbmk3TzBv+Fp+CADPqQm3OQ== test@test.com

如果我是对的,这不是公钥,但可以从这个字符串中获取公钥。

这个答案回答了我的问题https://stackoverflow.com/a/19387517/2735398
但答案似乎不起作用。我得到一个例外:

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format

在查看答案的 cmets 时,我不是唯一有问题的人......

如何修复异常?还是有其他方法可以从字符串中获取公钥?

【问题讨论】:

标签: java encryption ssh rsa public-key-encryption


【解决方案1】:

这是我的 SSH RSA -> RSAPublicKey 转换器实现。我在网上某处找到了关键格式说明,感谢提供它的人。

public class CertificateUtils {
    private static final int VALUE_LENGTH = 4;
    private static final byte[] INITIAL_PREFIX = new byte[]{0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2d, 0x72, 0x73, 0x61};
    private static final Pattern SSH_RSA_PATTERN = Pattern.compile("ssh-rsa[\\s]+([A-Za-z0-9/+]+=*)[\\s]+.*");

// SSH-RSA key format
//
//        00 00 00 07             The length in bytes of the next field
//        73 73 68 2d 72 73 61    The key type (ASCII encoding of "ssh-rsa")
//        00 00 00 03             The length in bytes of the public exponent
//        01 00 01                The public exponent (usually 65537, as here)
//        00 00 01 01             The length in bytes of the modulus (here, 257)
//        00 c3 a3...             The modulus

    public static RSAPublicKey parseSSHPublicKey(String key) throws InvalidKeyException {
        Matcher matcher = SSH_RSA_PATTERN.matcher(key.trim());
        if (!matcher.matches()) {
            throw new InvalidKeyException("Key format is invalid for SSH RSA.");
        }
        String keyStr = matcher.group(1);

        ByteArrayInputStream is = new ByteArrayInputStream(Base64.decodeBase64(keyStr));

        byte[] prefix = new byte[INITIAL_PREFIX.length];

        try {
            if (INITIAL_PREFIX.length != is.read(prefix) || !ArrayUtils.isEquals(INITIAL_PREFIX, prefix)) {
                throw new InvalidKeyException("Initial [ssh-rsa] key prefix missed.");
            }

            BigInteger exponent = getValue(is);
            BigInteger modulus = getValue(is);

            return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
        } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new InvalidKeyException("Failed to read SSH RSA certificate from string", e);
        }
    }

    private static BigInteger getValue(InputStream is) throws IOException {
        byte[] lenBuff = new byte[VALUE_LENGTH];
        if (VALUE_LENGTH != is.read(lenBuff)) {
            throw new InvalidParameterException("Unable to read value length.");
        }

        int len = ByteBuffer.wrap(lenBuff).getInt();
        byte[] valueArray = new byte[len];
        if (len != is.read(valueArray)) {
            throw new InvalidParameterException("Unable to read value.");
        }

        return new BigInteger(valueArray);
    }
}

希望这会有所帮助。

【讨论】:

  • 这是一个容易理解的答案;但是,为 VALUE_LENGTH 设置一个更具表现力的名称会有所改进。
【解决方案2】:

您必须将您的密钥转换为 pkcs8 规范。使用下面的命令

ssh-keygen -f private.key -e -m pkcs8 > test-pkcs8.pub

然后转成x509

openssl rsa -pubin -in test-pkcs8.pub -outform pem > test-x509.pem

然后您可以使用下面的代码在 Java 中将公钥读取为 RSAPublicKey

import java.io.IOException;

import java.net.URISyntaxException;

import java.nio.file.Files;

import java.nio.file.Paths;

import java.security.KeyFactory;

import java.security.NoSuchAlgorithmException;

import java.security.PrivateKey;

import java.security.interfaces.RSAPublicKey;

import java.security.spec.InvalidKeySpecException;

import java.security.spec.PKCS8EncodedKeySpec;

import java.security.spec.X509EncodedKeySpec;

import java.util.Base64;


/**

* This file is intended to be used on a IDE for testing purposes.

* ClassLoader.getSystemResource won't work in a JAR

*/

public class Main {


    public static void main(String[] args) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, URISyntaxException {


        String privateKeyContent = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("private_key_pkcs8.pem").toURI())));

        String publicKeyContent = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("public_key.pem").toURI())));


        privateKeyContent = privateKeyContent.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");

        publicKeyContent = publicKeyContent.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");;


        KeyFactory kf = KeyFactory.getInstance("RSA");


        PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyContent));

        PrivateKey privKey = kf.generatePrivate(keySpecPKCS8);


        X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyContent));

        RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(keySpecX509);


        System.out.println(privKey);

        System.out.println(pubKey);

    }

}

从以下两个链接得到答案

Converting ssh-rsa to X509 Spec in Java

Loading X509 spec key in Java as RSAPublicKey object

希望这会给你一些直觉。

【讨论】:

  • ssh-keygen -e -m pkcs8 实际上不会产生 PKCS8 而是 X.509 SPKI,这对于 Java publickey 已经是正确的,除了 PEM 包装,所以你的 openssl rsa 没有好处一点也不。然后,您的代码适用于 Q 想要的公钥,但不适用于私钥。 (虽然如果你确实有一个 PKCS8-clear 私钥。)正如我在 Q 上面评论的那样,工作部分已经在我对 2016 年 #39105031 的回答中。
【解决方案3】:

我找到了很多关于如何获取公钥的答案——但没有一个真正包含如何获取 openssh 公钥的部分作为字符串——它有一种特殊的格式。

感谢@Jcs 和@James K Polk

这取决于 BouncyCastle。它可能没有。

package cuul.stuff;

import lombok.SneakyThrows;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;

/**
 * Takes an private SSH key and cranks out the corresponding public one.
 *
 * Just what this command would have done: <pre>ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub</pre>
 *
 * @link https://stackoverflow.com/questions/3706177/how-to-generate-ssh-compatible-id-rsa-pub-from-java
 * @link https://stackoverflow.com/questions/7216969/getting-rsa-private-key-from-pem-base64-encoded-private-key-file/7221381#7221381
 *
 * Why - because I can.
 */
public class ExtractPublicFromPrivateSshKey {

    private static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n";
    private static final String END_RSA_PRIVATE_KEY = "-----END RSA PRIVATE KEY-----";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    @SneakyThrows
    public static String extract(String privateKeyString) {
        if (!privateKeyString.startsWith(BEGIN_RSA_PRIVATE_KEY)) {
            throw new InvalidKeySpecException("Can only extract public key from a RSA private. "
                    + "This is not an RSA key (header should have been '" + BEGIN_RSA_PRIVATE_KEY + "'");
        }

        privateKeyString = privateKeyString.replace(BEGIN_RSA_PRIVATE_KEY, "");
        privateKeyString = privateKeyString.replace(END_RSA_PRIVATE_KEY, "");
        privateKeyString = privateKeyString.trim();

        byte[] privateKeyBytes = Base64.getMimeDecoder().decode(privateKeyString);

        BCRSAPrivateCrtKey rsaPrivateKey = (BCRSAPrivateCrtKey) getPrivate(privateKeyBytes);

        //create a KeySpec and let the Factory due the Rest. You could also create the KeyImpl by your own.
        RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(
                new RSAPublicKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPublicExponent()));

        byte[] bytes = encodePublicKey(publicKey);
        return "ssh-rsa " + new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8) + " some@user";
    }

    private static PrivateKey getPrivate(byte[] privateKeyBytes)
            throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }

    /**
     * @link https://stackoverflow.com/questions/3706177/how-to-generate-ssh-compatible-id-rsa-pub-from-java
     *
     * The key format used by ssh is defined in the RFC #4253. The format for RSA public key is the following :

     * string    "ssh-rsa"
     * mpint     e  // key public exponent
     * mpint     n  // key modulus
     *
     * All data type encoding is defined in the section #5 of RFC #4251. string and mpint (multiple precision integer) types are encoded this way :
     *
     * 4-bytes word: data length (unsigned big-endian 32 bits integer)
     * n bytes     : binary representation of the data
     *
     * or instance, the encoding of the string "ssh-rsa" is:
     *
     * byte[] data = new byte[] {0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'};
     */
    private static byte[] encodePublicKey(RSAPublicKey key) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        /* encode the "ssh-rsa" string */
        byte[] sshrsa = new byte[] {0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'};
        out.write(sshrsa);
        /* Encode the public exponent */
        BigInteger e = key.getPublicExponent();
        byte[] data = e.toByteArray();
        encodeUInt32(data.length, out);
        out.write(data);
        /* Encode the modulus */
        BigInteger m = key.getModulus();
        data = m.toByteArray();
        encodeUInt32(data.length, out);
        out.write(data);
        return out.toByteArray();
    }

    private static void encodeUInt32(int value, OutputStream out) throws IOException {
        byte[] tmp = new byte[4];
        tmp[0] = (byte)((value >>> 24) & 0xff);
        tmp[1] = (byte)((value >>> 16) & 0xff);
        tmp[2] = (byte)((value >>> 8) & 0xff);
        tmp[3] = (byte)(value & 0xff);
        out.write(tmp);
    }
}

【讨论】:

    【解决方案4】:

    响应迟了,但我遇到了同样的问题并提出了以下问题:您需要 Apache commons-io 和 guava 库

    import java.io.ByteArrayInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.math.BigInteger;
    import java.security.spec.RSAPublicKeySpec;
    
    import org.apache.commons.io.IOUtils;
    
    import com.google.common.base.Splitter;
    import com.google.common.io.ByteSource;
    import com.google.common.io.ByteStreams;
    import com.google.common.base.Charsets;
    
    import static com.google.common.base.Preconditions.checkArgument;
    import static com.google.common.collect.Iterables.get;
    import static com.google.common.collect.Iterables.size;
    import static com.google.common.io.BaseEncoding.base64;
    
    public class SSHEncodedToRSAPublicConverter {
    
      private static final String SSH_MARKER = "ssh-rsa";
    
      private ByteSource supplier;
    
      public SSHEncodedToRSAPublicConverter(String fileName) {
        this(new File(fileName));
      }
    
      public SSHEncodedToRSAPublicConverter(File file) {
        try {
          byte[] data = IOUtils.toByteArray(new FileInputStream(file));
          this.supplier = ByteSource.wrap(data);
        } catch (Exception ex) {
          throw new RuntimeException(ex);
        }
      }
    
      public SSHEncodedToRSAPublicConverter(byte[] data) {
        this.supplier = ByteSource.wrap(data);
      }
    
      /**
       * Converts an SSH public key to a x.509 compliant format RSA public key spec
       * Source: https://github.com/jclouds/jclouds/blob/master/compute/src/main/java/org/jclouds/ssh/SshKeys.java
       * @return RSAPublicKeySpec
       */
      public RSAPublicKeySpec convertToRSAPublicKey() {
        try {
          InputStream stream = supplier.openStream();
          Iterable<String> parts = Splitter.on(' ').split(IOUtils.toString(stream, Charsets.UTF_8));
          checkArgument(size(parts) >= 2 && SSH_MARKER.equals(get(parts,0)), "bad format, should be: ssh-rsa AAAB3....");
          stream = new ByteArrayInputStream(base64().decode(get(parts, 1)));
          String marker = new String(readLengthFirst(stream));
          checkArgument(SSH_MARKER.equals(marker), "looking for marker %s but received %s", SSH_MAKER, marker);
          BigInteger publicExponent = new BigInteger(readLengthFirst(stream));
          BigInteger modulus = new BigInteger(readLengthFirst(stream));
          RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, publicExponent);
          return keySpec;
        } catch (Exception ex) {
          throw new RuntimeException(ex);
        }
      }
    
      private static byte[] readLengthFirst(InputStream in) throws IOException {
        int[] bytes = new int[]{ in.read(), in.read(), in.read(), in.read() };
        int length = 0;
        int shift = 24;
        for (int i = 0; i < bytes.length; i++) {
          length += bytes[i] << shift;
          shift -= 8;
        }
        byte[] val = new byte[length];
        ByteStreams.readFully(in, val);
        return val;
      }
    }
    

    然后要使用它,您可以执行以下操作:

    File keyFile = new File("id_rsa.pub");
    Keyspec spec = new SSHEncodedToRSAPublicConverter(keyFile).convertToRSAPublicKey();
    KeyFactory kf = KeyFactory.getInstance("RSA");
    Key key = kf.generatePublic(spec);
    

    我从以下链接获得了转换(特别感谢)部分:

    https://github.com/jclouds/jclouds/blob/master/compute/src/main/java/org/jclouds/ssh/SshKeys.java

    【讨论】:

      【解决方案5】:

      从通过 ssh-keygen 生成的.pub 文件中获取PublicKey 对象的代码。

      RSAPublicKeySpec 构造函数看起来像这样

      public RSAPublicKeySpec(BigInteger modulus, BigInteger publicExponent)
      

      所以我们需要从.pub文件中提取moduluspublicExponent并将其传递给构造函数以创建RSAPublicKeySpec。一旦我们有了规范,我们就可以使用KeyFactory生成PublicKey

      private PublicKey decodePublicKey() {
          try {
              // input stream of .pub file
              InputStream inputStream = new ClassPathResource("keys/test_public_key.key").getInputStream(); 
              String keyLine = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
              String[] parts = keyLine.split(" ");
              for (String part : parts) {
                  if (part.startsWith("AAAA")) {
                      byte[] decodeBuffer = Base64Utils.decode(part.getBytes());
                      ByteBuffer bb = ByteBuffer.wrap(decodeBuffer);
                      /* using 4 bytes from bb to generate integer which gives us length of key- 
                      format type, in this case len=7 as "ssh-rsa" has 7 chars  
                      */
                      int len = bb.getInt(); 
                      byte[] type = new byte[len];
                      bb.get(type);
                      if ("ssh-rsa".equals(new String(type))) {
                          // extracting exponent and modulus from remaining byte-buffer
                          BigInteger exponent = decodeBigInt(bb);
                          BigInteger modulus = decodeBigInt(bb);
                          RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
                          return KeyFactory.getInstance("RSA").generatePublic(spec);
                      } else {
                          throw new IllegalArgumentException("Only supporta RSA");
                      }
                  }
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
          return null;
      }
      
      private BigInteger decodeBigInt(ByteBuffer bb) {
          // use first 4 bytes to generate an Integer that gives the length of bytes to create BigInteger
          int len = bb.getInt();
          byte[] bytes = new byte[len];
          bb.get(bytes);
          return new BigInteger(bytes);
      }
      

      参考资料:

      【讨论】:

        猜你喜欢
        • 2014-11-27
        • 2014-09-27
        • 2012-01-18
        • 1970-01-01
        • 2016-01-30
        • 1970-01-01
        • 2021-03-30
        • 2016-03-16
        • 2015-06-07
        相关资源
        最近更新 更多