2010-08-20 67 views
12

如何使用系統authorized_keys文件中的條目實現java.security.PublicKey?我特別希望將authorized_keys文件中的公鑰與Apache SSHD PublickeyAuthenticator接口中提供的公鑰進行比較。使用Java安全性授權密鑰中的公鑰

+0

你也可能要問這個米娜郵件列表上。這是一個非常好的問題,因爲這是安全的Java sshd實現中少數缺失的組件之一。 – 2010-08-20 13:59:00

回答

16

我很驚訝這裏沒有任何明顯的表現。我很好奇並實施了一種解碼authorized_keys文件的方法。這取決於Apache Commons Codec for Base64解碼。

import java.io.File; 
import java.math.BigInteger; 
import java.security.KeyFactory; 
import java.security.PublicKey; 
import java.security.spec.DSAPublicKeySpec; 
import java.security.spec.RSAPublicKeySpec; 
import java.util.Scanner; 

import org.apache.commons.codec.binary.Base64; 

public class AuthorizedKeysDecoder { 
    private byte[] bytes; 
    private int pos; 

    public PublicKey decodePublicKey(String keyLine) throws Exception { 
     bytes = null; 
     pos = 0; 

     // look for the Base64 encoded part of the line to decode 
     // both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes 
     for (String part : keyLine.split(" ")) { 
      if (part.startsWith("AAAA")) { 
       bytes = Base64.decodeBase64(part); 
       break; 
      } 
     } 
     if (bytes == null) { 
      throw new IllegalArgumentException("no Base64 part to decode"); 
     } 

     String type = decodeType(); 
     if (type.equals("ssh-rsa")) { 
      BigInteger e = decodeBigInt(); 
      BigInteger m = decodeBigInt(); 
      RSAPublicKeySpec spec = new RSAPublicKeySpec(m, e); 
      return KeyFactory.getInstance("RSA").generatePublic(spec); 
     } else if (type.equals("ssh-dss")) { 
      BigInteger p = decodeBigInt(); 
      BigInteger q = decodeBigInt(); 
      BigInteger g = decodeBigInt(); 
      BigInteger y = decodeBigInt(); 
      DSAPublicKeySpec spec = new DSAPublicKeySpec(y, p, q, g); 
      return KeyFactory.getInstance("DSA").generatePublic(spec); 
     } else { 
      throw new IllegalArgumentException("unknown type " + type); 
     } 
    } 

    private String decodeType() { 
     int len = decodeInt(); 
     String type = new String(bytes, pos, len); 
     pos += len; 
     return type; 
    } 

    private int decodeInt() { 
     return ((bytes[pos++] & 0xFF) << 24) | ((bytes[pos++] & 0xFF) << 16) 
       | ((bytes[pos++] & 0xFF) << 8) | (bytes[pos++] & 0xFF); 
    } 

    private BigInteger decodeBigInt() { 
     int len = decodeInt(); 
     byte[] bigIntBytes = new byte[len]; 
     System.arraycopy(bytes, pos, bigIntBytes, 0, len); 
     pos += len; 
     return new BigInteger(bigIntBytes); 
    } 

    public static void main(String[] args) throws Exception { 
     AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder(); 
     File file = new File("authorized_keys"); 
     Scanner scanner = new Scanner(file).useDelimiter("\n"); 
     while (scanner.hasNext()) { 
      System.out.println(decoder.decodePublicKey(scanner.next())); 
     } 
     scanner.close(); 
    } 
} 
+3

沒有必要只爲此添加公共庫作爲依賴項。看看DatatypeConverter#parseBase64Binary http://docs.oracle.com/javase/7/docs/api/javax/xml/bind/DatatypeConverter.html#parseBase64Binary(java.lang.String) – LanguagesNamedAfterCofee 2012-09-13 21:32:02

+0

謝謝,男人!爲我節省了數小時的工作時間。 – 2013-01-27 00:37:22

+0

如果可能,會給5 ups!非常感謝! – Daniel 2014-01-14 07:09:05

2

要WhiteFang34,

你的代碼是真棒,對我太有用,但它有一個微小的錯誤。 Base64.decodeBase64方法只接收字節數組。所以我就這樣修復了。

for (String part : keyLine.split(" ")) { 
     if (part.startsWith("AAAA")) { 
      byte [] bytePart = part.getBytes(); 
      bytes = Base64.decodeBase64(bytePart); 
      break; 
     } 
    } 

無論如何,感謝您編寫代碼。我希望你能將這段代碼上傳到github或其他地方,或者讓我上傳到我的github回購站。

+0

它看起來取決於你使用的是哪個'Base64'類。 Commons Codec 1.4及更高版本中包含的一個包含我使用的方法:['decodeBase64(java.lang.String)'](http://commons.apache.org/codec/api-release/org/apache/commons/ codec/binary/Base64.html#decodeBase64(java.lang.String)) – WhiteFang34 2012-06-12 06:46:46

5

如果想逆轉這一過程,即編碼PublicKey Java對象到Linux authorized_keys輸入格式,可以使用此代碼:

/** 
    * Encode PublicKey (DSA or RSA encoded) to authorized_keys like string 
    * 
    * @param publicKey DSA or RSA encoded 
    * @param user username for output authorized_keys like string 
    * @return authorized_keys like string 
    * @throws IOException 
    */ 
    public static String encodePublicKey(PublicKey publicKey, String user) 
      throws IOException { 
     String publicKeyEncoded; 
     if(publicKey.getAlgorithm().equals("RSA")){ 
      RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey; 
      ByteArrayOutputStream byteOs = new ByteArrayOutputStream(); 
      DataOutputStream dos = new DataOutputStream(byteOs); 
      dos.writeInt("ssh-rsa".getBytes().length); 
      dos.write("ssh-rsa".getBytes()); 
      dos.writeInt(rsaPublicKey.getPublicExponent().toByteArray().length); 
      dos.write(rsaPublicKey.getPublicExponent().toByteArray()); 
      dos.writeInt(rsaPublicKey.getModulus().toByteArray().length); 
      dos.write(rsaPublicKey.getModulus().toByteArray()); 
      publicKeyEncoded = new String(
        Base64.encodeBase64(byteOs.toByteArray())); 
      return "ssh-rsa " + publicKeyEncoded + " " + user; 
     } 
     else if(publicKey.getAlgorithm().equals("DSA")){ 
      DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey; 
      DSAParams dsaParams = dsaPublicKey.getParams(); 

      ByteArrayOutputStream byteOs = new ByteArrayOutputStream(); 
      DataOutputStream dos = new DataOutputStream(byteOs); 
      dos.writeInt("ssh-dss".getBytes().length); 
      dos.write("ssh-dss".getBytes()); 
      dos.writeInt(dsaParams.getP().toByteArray().length); 
      dos.write(dsaParams.getP().toByteArray()); 
      dos.writeInt(dsaParams.getQ().toByteArray().length); 
      dos.write(dsaParams.getQ().toByteArray()); 
      dos.writeInt(dsaParams.getG().toByteArray().length); 
      dos.write(dsaParams.getG().toByteArray()); 
      dos.writeInt(dsaPublicKey.getY().toByteArray().length); 
      dos.write(dsaPublicKey.getY().toByteArray()); 
      publicKeyEncoded = new String(
        Base64.encodeBase64(byteOs.toByteArray())); 
      return "ssh-dss " + publicKeyEncoded + " " + user; 
     } 
     else{ 
      throw new IllegalArgumentException(
        "Unknown public key encoding: " + publicKey.getAlgorithm()); 
     } 
    } 
2

同樣的解決方案,但代表們decodeInt()到DataInputStream類。 只要已經知道RSA算法,我就從原始代碼中刪除KeyFactory的BouncyCastleProvider。

原始來源:https://github.com/ragnar-johannsson/CloudStack/blob/master/utils/src/com/cloud/utils/crypt/RSAHelper.java

private static RSAPublicKey readKey(String key) throws Exception { 
    // key = "ssh-rsa <myBase64key> <email>" 
    byte[] encKey = Base64.decodeBase64(key.split(" ")[1]); 
    DataInputStream dis = new DataInputStream(new ByteArrayInputStream(encKey)); 

    byte[] header = readElement(dis); 
    String pubKeyFormat = new String(header); 
    if (!pubKeyFormat.equals("ssh-rsa")) 
    throw new RuntimeException("Unsupported format"); 

    byte[] publicExponent = readElement(dis); 
    byte[] modulus = readElement(dis); 

    KeySpec spec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent)); 
    KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 
    RSAPublicKey pubKey = (RSAPublicKey) keyFactory.generatePublic(spec); 

    return pubKey; 
} 

private static byte[] readElement(DataInput dis) throws IOException { 
    int len = dis.readInt(); 
    byte[] buf = new byte[len]; 
    dis.readFully(buf); 
    return buf; 
} 
+0

謝謝!經過幾個小時的反擊之後,你的代碼對我來說是有意義的,並且解決了這個問題! 'readElement'方法的好主意。 – CullenJ 2017-05-02 22:46:11