简体   繁体   English

Java ssh-rsa 字符串到公钥

[英]Java ssh-rsa string to public key

I want to get the public key of the content of an .pub file.我想获取.pub文件内容的公钥。 This is an example what the content of a .pub file looks like(generated with ssh-keygen ):这是.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

If I am right this is not the public key, but it is possible to get the public key from this string.如果我是对的,这不是公钥,但可以从此字符串中获取公钥。

This answer gives answer to my question https://stackoverflow.com/a/19387517/2735398这个答案回答了我的问题https://stackoverflow.com/a/19387517/2735398
But the answer doesn't seem to work.但答案似乎不起作用。 I get an exception:我得到一个例外:

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

When looking at the comments of the answer I am not the only person with the problem...在查看答案的评论时,我不是唯一有问题的人......

How can I fix the exception?如何修复异常? Or is there another way to get the public key from the string?还是有另一种方法可以从字符串中获取公钥?

You have to convert your key to pkcs8 spec.您必须将您的密钥转换为 pkcs8 规范。 Use below command使用下面的命令

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

Then convert it to x509然后将其转换为 x509

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

You can then use below code to read the public key as RSAPublicKey in Java然后,您可以使用以下代码在 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);

    }

}

Got the answer from below two links从以下两个链接得到答案

Converting ssh-rsa to X509 Spec in Java在 Java 中将 ssh-rsa 转换为 X509 规范

Loading X509 spec key in Java as RSAPublicKey object 在 Java 中加载 X509 规范密钥作为 RSAPublicKey 对象

Hope this will give you some intuition.希望这会给你一些直觉。

Here is my SSH RSA -> RSAPublicKey converter implementation.这是我的 SSH RSA -> RSAPublicKey 转换器实现。 I've found key format description somewhere in the net, so thanks to the one who provided it.我在网上某处找到了关键格式描述,所以感谢提供它的人。

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);
    }
}

Hope this helps.希望这可以帮助。

I found a lot of answers how to get the public key - but none of them actually contained the part how to get the openssh public key as a string - it got a special format.我找到了很多如何获取公钥的答案——但实际上没有一个答案包含如何将 openssh 公钥作为字符串获取的部分——它有一种特殊的格式。

Cudos to @Jcs and @James K Polk向@Jcs 和@James K Polk 致敬

This depends on BouncyCastle.这取决于 BouncyCastle。 It could probably be done without.没有它可能可以做到。

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);
    }
}

Late response but I had the same issue and came up with the following: You'll need Apache commons-io and guava libraries迟到的回应,但我遇到了同样的问题,并提出了以下问题:您需要 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;
  }
}

Then to use it you can do something like:然后要使用它,您可以执行以下操作:

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

I got the conversion (special thanks) portion from the following link:我从以下链接获得了转换(特别感谢)部分:

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

Code for getting PublicKey object from .pub file generated through ssh-keygen.从通过 ssh-keygen 生成的.pub文件获取PublicKey对象的代码。

RSAPublicKeySpec constructor looks like this RSAPublicKeySpec构造函数看起来像这样

public RSAPublicKeySpec(BigInteger modulus, BigInteger publicExponent)

So we need to extract modulus and publicExponent from .pub file and pass it to the constructor to create RSAPublicKeySpec .所以我们需要从.pub文件中提取moduluspublicExponent并将其传递给构造函数以创建RSAPublicKeySpec Once we have the spec we can generate PublicKey using KeyFactory .一旦我们有了规范,我们就可以使用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);
}

References :参考资料:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM