[memo] RSA-Schlüsselpaar für SSH in Java generieren

Einführung

Übrigens, wenn Sie normalerweise "ssh-keygen" verwenden, sieht es wie folgt aus.

ssh-keygen -t rsa -b 4096

RSA-Überprüfung "Übersicht über RSA-Schlüsselpaare"

Ich werde die Erklärung aus dem folgenden Qiita-Artikel ausleihen.

(Von hier ausgeliehen)

Wenn die beiden großen Primzahlen $ p $ und $ q $ sind

N = pq \tag{1}

Sei $ L $ die Variable, die (2) unten erfüllt.

L = lcm(p-1, q-1) \tag{2}

Außerdem sei $ E $ eine Variable, die die folgende Gleichung (3) erfüllt.

gcd(E, L) = 1 \tag{3}

Schließlich sei $ D $ die Variable, die Gleichung (4) erfüllt.

ED≡1 (mod L) \tag{4}

Das bisher erhaltene $ (E, N) $ -Paar ist der * öffentliche Schlüssel *, und das $ (D, N) $ -Paar ist der * private Schlüssel *.

(Bisher ausgeliehen)

OpenSSH-Dateiformat für private Schlüssel

Wie ich am Anfang geschrieben habe, nehme ich das PEM-Format und keine Passphrase an.

Grundsätzlich sieht es aus wie PKCS # 8 im PEM-Format.

Weitere Informationen finden Sie oben unter "PKCS # 8-Format" unter "RSA-Dateiformat mit öffentlichem Schlüssel und Fingerabdruck".

Übrigens wird für Java RSAPrivateKey-Instanzen die Methode "getEncoded ()" verwendet, um die Binärdaten abzurufen, sodass Sie in der Lage sein sollten, eine MIME-ähnliche Base64-Codierung durchzuführen.

    public Optional<String> encodePrivateKey(final PrivateKey privateKey) {

        final String keyType;
        if (privateKey instanceof RSAPrivateKey) {
            keyType = "RSA";
        } else if (privateKey instanceof DSAPrivateKey) {
            keyType = "DSA";
        } else if (privateKey instanceof ECPrivateKey) {
            keyType = "EC";
        } else {
            return Optional.empty();
        }

        final byte[] privateBytes = privateKey.getEncoded();
        final String privateBase64 = Base64.getMimeEncoder()
                .encodeToString(privateBytes);

        final String encodedPrivateKey = "-----BEGIN " + keyType + " PRIVATE KEY-----\n"
                + privateBase64 + "\n"
                + "-----END " + keyType + " PRIVATE KEY-----";
        return Optional.of(encodedPrivateKey);
    }

OpenSSH RSA-Dateiformat mit öffentlichem Schlüssel

Im Gegensatz zu dem in RFC4716 beschriebenen Format gibt es im OpenSSH-Format kein "BEGIN" / "END" -Trennzeichen davor und danach, sondern am Anfang. Es hat die Signatur "ssh-rsa".

Nachfolgende Daten ähneln dem RFC4716-Format (aus dem obigen Artikel "RSA Public Key-Dateiformat und Fingerabdruck").

Details zu diesem Format finden Sie unter "6.6. Algorithmen für öffentliche Schlüssel" in RFC4253.

string "ssh-rsa" mpint e mpint n

Das Format dieses "Strings" oder "mpint" ist in "5. In den SSH-Protokollen verwendete Datentypdarstellungen" von RFC4251 beschrieben. Einfach ausgedrückt ist es wie eine 4-Byte-Big-Endian-Länge + Byte-Zeichenfolge.

Es scheint, dass dies in Base64 codiert werden sollte. Im Fall des OpenSSH-Formats muss Base64 anscheinend eine Zeile ausgeben, ohne die Zeile zu umbrechen.

$ e $ und $ n $ entsprechen $ E $ und $ N $, die oben in der "Übersicht über RSA-Schlüsselpaare" beschrieben wurden.

Übrigens können die Werte von $ e $ und $ n $ mit der Methode "getPublicExponent ()" bzw. der Methode "getModulus ()" für die Java RSAPublicKey-Instanz abgerufen werden.

(Manchmal hat der öffentliche Schlüssel am Ende so etwas wie eine E-Mail-Adresse, aber dies sollte ein Kommentar sein.)

    public String encodeRsaPublicKey(final RSAPublicKey publicKey) {

        final String sig = "ssh-rsa";
        final byte[] sigBytes = sig.getBytes(StandardCharsets.US_ASCII);
        final byte[] eBytes = publicKey.getPublicExponent().toByteArray();
        final byte[] nBytes = publicKey.getModulus().toByteArray();

        final int size = 4 + sigBytes.length
                + 4 + eBytes.length
                + 4 + nBytes.length;

        final byte[] publicKeyBytes = ByteBuffer.allocate(size)
                .order(ByteOrder.BIG_ENDIAN)
                .putInt(sigBytes.length).put(sigBytes)
                .putInt(eBytes.length).put(eBytes)
                .putInt(nBytes.length).put(nBytes)
                .array();

        final String publicKeyBase64 = Base64.getEncoder()
                .encodeToString(publicKeyBytes);

        final String publicKeyEncoded = sig + " " + publicKeyBase64;
        return publicKeyEncoded;
    }

OpenSSH DSA-Dateiformat mit öffentlichem Schlüssel (Bonus)

DSA wird in RFC4253 sowie in RSA beschrieben.

string "ssh-dss" mpint p mpint q mpint g mpint y

    public String encodeDsaPublicKey(final DSAPublicKey publicKey) {

        final String sig = "ssh-dss";
        final byte[] sigBytes = sig.getBytes(StandardCharsets.US_ASCII);
        final DSAParams params = publicKey.getParams();
        final byte[] pBytes = params.getP().toByteArray();
        final byte[] qBytes = params.getQ().toByteArray();
        final byte[] gBytes = params.getG().toByteArray();
        final byte[] yBytes = publicKey.getY().toByteArray();

        final int size = 4 + sigBytes.length
                + 4 + pBytes.length
                + 4 + qBytes.length
                + 4 + gBytes.length
                + 4 + yBytes.length;

        final byte[] publicKeyBytes = ByteBuffer.allocate(size)
                .order(ByteOrder.BIG_ENDIAN)
                .putInt(sigBytes.length).put(sigBytes)
                .putInt(pBytes.length).put(pBytes)
                .putInt(qBytes.length).put(qBytes)
                .putInt(gBytes.length).put(gBytes)
                .putInt(yBytes.length).put(yBytes)
                .array();

        final String publicKeyBase64 = Base64.getEncoder()
                .encodeToString(publicKeyBytes);

        final String publicKeyEncoded = sig + " " + publicKeyBase64;
        return publicKeyEncoded;
    }

OpenSSH ECDSA (Elliptical Curve Code) Dateiformat für öffentliche Schlüssel (Bonus)

Das Format des öffentlichen Schlüssels des Codes für die elliptische Kurve scheint in RFC5656 definiert zu sein.

string   "ecdsa-sha2-[identifier]"
byte[n]  ecc_key_blob

Der Inhalt von ecc_key_blob ist

string   [identifier]
string   Q

"Bezeichner" scheint aus der Art der elliptischen Kurve erhalten zu werden. Der NIST-Teil der folgenden Tabelle ist der "Bezeichner".

NIST SEC OID
nistp256 secp256r1 1.2.840.10045.3.1.7
nistp384 secp384r1 1.3.132.0.34
nistp521 secp521r1 1.3.132.0.35

Übrigens scheinen diese drei Typen [erforderliche] Kurven zu sein (eine elliptische Kurve, die implementiert werden muss, wenn ECDSA von SSH-Servern und -Clients unterstützt wird).

Es scheint, dass es andere [empfohlene] elliptische Kurven gibt, aber ich werde sie weglassen, weil es viele gibt. Übrigens verwendet der empfohlene Kurven-Bezeichner die OID-Spalte anstelle der NIST-Spalte (z. B. sollte es sich um eine Signatur wie "ecdsa-sha2-1.3.132.0.38" handeln). Siehe RFC für Details.

Es scheint auch, dass "Q" "ein codierter Punkt auf einer elliptischen Kurve in eine Oktettzeichenfolge" ist, aber die Details der Spezifikation sind "http://www.secg.org/download/aid-780/sec1-v2" Es wird in .pdf` erwähnt. Als ich tatsächlich danach gesucht habe, scheint es, dass es zu https://www.secg.org/sec1-v2.pdf verschoben wurde. Weitere Informationen finden Sie in 2.3.3 dieses Dokuments. Es scheint, dass das Format unterschiedlich ist, je nachdem, ob Sie die Funktion "Punktkomprimierung" verwenden oder nicht. Wenn Sie sie jedoch nicht verwenden, scheint es sich um eine relativ einfache Struktur zu handeln.

Unter der Annahme, dass "Bezeichner" entschieden wird, sieht es vorerst wie folgt aus.

    public String encodeEcPublicKey(
            final String identifier,
            final ECPublicKey publicKey) {

        final String sig = "ecdsa-sha2-" + identifier;
        final BigInteger x = publicKey.getW().getAffineX();
        final BigInteger y = publicKey.getW().getAffineY();

        final byte[] sigBytes = sig.getBytes(StandardCharsets.US_ASCII);
        final byte[] identifierBytes = identifier.getBytes(StandardCharsets.US_ASCII);
        final byte[] xBytes = x.toByteArray();
        final byte[] yBytes = y.toByteArray();

        final int size = 4 + sigBytes.length
                + 4 + identifierBytes.length
                + 4 + 1 + xBytes.length + yBytes.length;

        final byte[] publicKeyBytes = ByteBuffer.allocate(size)
                .order(ByteOrder.BIG_ENDIAN)
                .putInt(sigBytes.length).put(sigBytes)
                .putInt(identifierBytes.length).put(identifierBytes)
                .putInt(1 + xBytes.length + yBytes.length)
                .put((byte) 0x04)
                .put(xBytes)
                .put(yBytes)
                .array();

        final String publicKeyBase64 = Base64.getEncoder()
                .encodeToString(publicKeyBytes);

        final String publicKeyEncoded = sig + " " + publicKeyBase64;
        return publicKeyEncoded;

    }

Und "Bezeichner" gibt den Typ der elliptischen Kurve an, und es scheint, dass die Beurteilung durch den Inhalt von "ECParameterSpec" erfolgen kann, der aus dem privaten oder öffentlichen Schlüssel erhalten werden kann.

Für bestimmte Parameter scheint es notwendig zu sein, die Spezifikationen der elliptischen Kurve zu sehen.

Details zu den Spezifikationen finden Sie unter "http: // www.secg.org / download / aid-386 / sec2_final.pdf". Als ich tatsächlich danach suchte, wurde es auch verschoben https://www.secg.org/SEC2-Ver-1.0.pdf Es war in.

Um die erforderliche Kurven-ID auf der Grundlage von ECParameterSpec zu bestimmen, sieht es vorerst wie folgt aus.

    private Optional<String> decideEcIdentifier(final ECParameterSpec params) {

        if (!(params.getCurve().getField() instanceof ECFieldFp)) {
            return Optional.empty();
        }
        final ECFieldFp field = (ECFieldFp) params.getCurve().getField();
        final BigInteger p = field.getP();
        final BigInteger a = params.getCurve().getA();
        final BigInteger b = params.getCurve().getB();
        final BigInteger g1 = params.getGenerator().getAffineX();
        final BigInteger g2 = params.getGenerator().getAffineY();
        final BigInteger n = params.getOrder();
        final int h = params.getCofactor();

        final List<Number> tuple = Arrays.asList(p, a, b, g1, g2, n, h);
        final String identifier = identifierMap.get(tuple);
        return Optional.ofNullable(identifier);
    }

    private static final Map<List<Number>, String> identifierMap;
    static {
        final Map<List<Number>, String> map = new LinkedHashMap<>();

        map.put(Arrays.asList(
                new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951"),
                new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
                new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"),
                new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
                new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"),
                new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
                1), "nistp256");
        map.put(Arrays.asList(
                new BigInteger(
                        "39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319"),
                new BigInteger(
                        "39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112316"),
                new BigInteger(
                        "27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575"),
                new BigInteger(
                        "26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087"),
                new BigInteger(
                        "8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871"),
                new BigInteger(
                        "39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643"),
                1), "nistp384");
        map.put(Arrays.asList(
                new BigInteger(
                        "6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"),
                new BigInteger(
                        "6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057148"),
                new BigInteger(
                        "1093849038073734274511112390766805569936207598951683748994586394495953116150735016013708737573759623248592132296706313309438452531591012912142327488478985984"),
                new BigInteger(
                        "2661740802050217063228768716723360960729859168756973147706671368418802944996427808491545080627771902352094241225065558662157113545570916814161637315895999846"),
                new BigInteger(
                        "3757180025770020463545507224491183603594455134769762486694567779615544477440556316691234405012945539562144444537289428522585666729196580810124344277578376784"),
                new BigInteger(
                        "6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449"),
                1), "nistp521");
        identifierMap = Collections.unmodifiableMap(map);
    }

OpenSSH-Dateiformat mit öffentlichem Schlüssel

Dies ist ein Beispiel für die Codierung der oben genannten drei Formate.

    public Optional<String> encodePublicKey(final PublicKey publicKey) {

        if (publicKey instanceof RSAPublicKey) {
            final RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
            return Optional.of(encodeRsaPublicKey(rsaPublicKey));

        } else if (publicKey instanceof DSAPublicKey) {
            final DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
            return Optional.of(encodeDsaPublicKey(dsaPublicKey));

        } else if (publicKey instanceof ECPublicKey) {
            final ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
            return decideEcIdentifier(ecPublicKey.getParams())
                    .map(identifier -> encodeEcPublicKey(identifier, ecPublicKey));
        }
        return Optional.empty();
    }

Generieren Sie als Bonus einen öffentlichen Schlüssel aus dem privaten RSA-Schlüssel

Wenn Sie ssh-keygen verwenden, sieht es wie folgt aus

ssh-keygen -yf id_rsa > id_rsa.pub

Wenn Sie es in Java tun, sieht es so aus.

    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
    generator.initialize(4096);
    KeyPair keyPair = generator.genKeyPair();
    RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyPair.getPrivate();
    BigInteger publicExponent = privateKey.getPublicExponent();
    BigInteger modulus = privateKey.getModulus();
    KeyFactory factory = KeyFactory.getInstance("RSA");
    RSAPublicKeySpec spec = new RSAPublicKeySpec(
            modulus,
            publicExponent);
    RSAPublicKey publicKey = (RSAPublicKey) factory.generatePublic(spec);

Der Punkt ist, dass der normalerweise generierte private RSA-Schlüssel auch "RSAPrivateCrtKey" implementiert.

Weiterer Bonus

Ist ED25519 ein RFC, der noch nicht veröffentlicht wurde?

Internet-Entwurf [https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448-08](https://tools.ietf.org/html/draft-ietf-curdle-ssh -ed25519-ed448-08) sieht wie folgt aus.

string "ssh-ed25519" string key

In ähnlicher Weise sieht ED448 wie folgt aus.

string "ssh-ed448" string key

Recommended Posts

[memo] RSA-Schlüsselpaar für SSH in Java generieren
Wichtige Punkte für die Einführung von gRPC in Java
Beispiel für die Erstellung / Verschlüsselung / Entschlüsselung von RSA-Schlüsselpaaren (JAVA)
(Memo) Java für Anweisung
Anfänger spielen Janken-Spiele in Java
[Für Anfänger] Führen Sie Selenium auf Java aus
Verschlüsselung mit RSA-Verschlüsselung in Java
Einstellungen für das SSL-Debugging in Java
Doppelte Karte sortiert nach Schlüssel in Java
Erste Schritte für tiefes Lernen in Java
Notizen im Kopf organisieren (Java-Arrangement)
Memo für die Migration von Java nach Kotlin
Nachrichtendialog mit Java anzeigen (persönliches Memo)
Java-Memo
ChatWork4j für die Verwendung der ChatWork-API in Java
Techniken zum Lesen von Java-Quellcode in Eclipse
Lösung für NetBeans 8.2 funktioniert nicht in Java 9-Umgebung
Notizen im Kopf organisieren (Java - Instance Edition)
SourceMapPathOverrides Festlegen von Memos im VSCode Debugger für Chrome
Organisiertes Memo im Kopf (Java - Datentyp)
Stellen Sie mit vim die Popup-Anzeige für die Java-Sprache ein.
Generieren Sie Java-Clientcode für die SOAP-API von SalesForce
Generieren Sie OffsetDateTime aus Clock und LocalDateTime in Java
Vergleichen Sie die PDF-Ausgabe in Java für Snapshot-Tests
Aktivieren / Deaktivieren von SNI in Java für jede Kommunikation
Punkte, die bei Java beachtet werden müssen, sind gleich