可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to get the public key of the content of an .pub
file. This is an example what the content of a .pub
file looks like(generated with 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
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?
回答1:
You have to convert your key to pkcs8 spec. Use below command
ssh-keygen -f private.key -e -m pkcs8 > test-pkcs8.pub
Then convert it to 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
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
Loading X509 spec key in Java as RSAPublicKey object
Hope this will give you some intuition.
回答2:
Late response but I had the same issue and came up with the following: You'll need Apache commons-io and guava libraries
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
回答3:
Here is my SSH RSA -> RSAPublicKey converter implementation. 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.
回答4:
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.
Cudos to @Jcs and @James K Polk
This depends on 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);
}
}