[mémo] Générer une paire de clés RSA pour SSH en Java

introduction

Au fait, si vous utilisez normalement ssh-keygen, cela ressemble à ce qui suit.

ssh-keygen -t rsa -b 4096

Revue RSA "Présentation de la paire de clés RSA"

J'emprunterai l'explication de l'article suivant de Qiita.

(Emprunté d'ici)

Si les deux grands nombres premiers sont $ p $ et $ q $

N = pq \tag{1}

Soit $ L $ la variable qui satisfait (2) ci-dessous.

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

De plus, soit $ E $ une variable qui satisfait l'équation suivante (3).

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

Enfin, soit $ D $ la variable qui satisfait l'équation (4).

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

La paire $ (E, N) $ obtenue jusqu'à présent est la * clé publique *, et la paire $ (D, N) $ est la * clé privée *.

(Emprunté jusqu'à présent)

Format de fichier de clé privée OpenSSH

Comme je l'ai écrit au début, je suppose que le format PEM et aucune phrase de passe.

Fondamentalement, cela ressemble à PKCS # 8 au format PEM.

Pour plus de détails, voir «Format PKCS # 8» dans «Format de fichier de clé publique RSA et empreinte digitale» ci-dessus.

À propos, pour les instances Java RSAPrivateKey, la méthode getEncoded () est utilisée pour obtenir les données binaires, vous devriez donc être capable de faire un encodage Base64 de type MIME.

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

Format de fichier de clé publique OpenSSH RSA

Contrairement au format décrit dans RFC4716, dans le cas du format OpenSSH, il n'y a pas de délimiteur "BEGIN" / "END" avant et après, mais au début. Il a la signature "ssh-rsa".

Les données suivantes sont similaires au format RFC4716 (de l'article «RSA Public Key File Format and fingerprint» ci-dessus).

Les détails de ce format peuvent être trouvés dans "6.6. Algorithmes de clé publique" dans RFC4253.

string "ssh-rsa" mpint e mpint n

Le format de cette "chaîne" ou "mpint" est décrit dans "5. Représentations des types de données utilisées dans les protocoles SSH" de RFC4251. En termes simples, c'est comme une longueur de 4 octets + une chaîne d'octets.

Il semble que cela devrait être encodé en Base64. Dans le cas du format OpenSSH, Base64 semble devoir sortir une ligne sans encapsuler la ligne.

$ e $ et $ n $ correspondent à $ E $ et $ N $ décrits dans la "Présentation des paires de clés RSA" ci-dessus.

Au fait, les valeurs de $ e $ et $ n $ peuvent être obtenues par la méthode getPublicExponent () et la méthode getModulus (), respectivement, pour l'instance Java RSAPublicKey.

(Parfois, la clé publique a quelque chose comme une adresse e-mail à la fin, mais cela devrait être un commentaire)

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

Format de fichier de clé publique OpenSSH DSA (bonus)

DSA est décrit dans RFC4253 ainsi que RSA.

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) Format de fichier de clé publique (Bonus)

Le format de la clé publique du code de la courbe elliptique semble être défini dans la RFC5656.

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

ʻEcc_key_blob` est à l'intérieur

string   [identifier]
string   Q

«Identifiant» semble être obtenu à partir du type de courbe elliptique. La partie NIST du tableau ci-dessous est «identifiant».

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

En passant, ces trois types semblent être des courbes [obligatoires](une courbe elliptique qui doit être implémentée lorsque ECDSA est pris en charge par les serveurs et clients SSH).

Il semble qu'il existe d'autres courbes elliptiques [recommandées], mais je les omettrai car elles sont nombreuses. Au fait, la courbe recommandée ʻidentifier utilise la colonne OID au lieu de la colonne NIST (exemple: il devrait s'agir d'une signature comme ʻecdsa-sha2-1.3.132.0.38). Voir RFC pour plus de détails.

De plus, il semble que Q soit" un point codé sur une courbe elliptique dans une chaîne de caractères d'octet ", mais les détails de la spécification sont` http://www.secg.org/download/aid-780/sec1-v2 Il est mentionné en .pdf ». Quand je l'ai recherché, il semble qu'il soit passé à https://www.secg.org/sec1-v2.pdf. Plus d'informations peuvent être trouvées dans 2.3.3 de ce document. Il semble que le format diffère selon que vous utilisez ou non la fonction appelée "compression ponctuelle", mais si vous ne l'utilisez pas, cela semble être une structure relativement simple.

Pour le moment, en supposant que «identificateur» a été décidé, il ressemble à ce qui suit.

    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;

    }

Et ʻidentifier indique le type de courbe elliptique, et il semble que le contenu de ʻECParameterSpec qui peut être obtenu à partir de la clé privée ou de la clé publique puisse être utilisé pour le jugement.

Pour des paramètres spécifiques, il semble nécessaire de voir les spécifications de la courbe elliptique.

Les détails des spécifications peuvent être consultés sur «http: // www.secg.org / download / aid-386 / sec2_final.pdf». Quand je l'ai recherché, il a également été déplacé https://www.secg.org/SEC2-Ver-1.0.pdf C'était en.

Pour le moment, pour déterminer la courbe requise ʻidentifier basée sur ʻECParameterSpec, cela ressemble à ce qui suit.

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

Format de fichier de clé publique OpenSSH

Ceci est un exemple d'encodage des trois formats ci-dessus.

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

En prime, générez une clé publique à partir de la clé privée RSA

Si vous utilisez ssh-keygen, cela ressemble à ce qui suit

ssh-keygen -yf id_rsa > id_rsa.pub

Si vous le faites en Java, cela ressemble à ceci.

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

Le fait est que la clé privée RSA normalement générée implémente également RSAPrivateCrtKey.

Bonus supplémentaire

ED25519 est-il une RFC pas encore publiée?

Projet Internet [https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448-08](https://tools.ietf.org/html/draft-ietf-curdle-ssh -ed25519-ed448-08) ressemble à ce qui suit.

string "ssh-ed25519" string key

De même, ED448 ressemble à ce qui suit.

string "ssh-ed448" string key

Recommended Posts

[mémo] Générer une paire de clés RSA pour SSH en Java
Points clés pour l'introduction de gRPC en Java
Exemple de création / chiffrement / déchiffrement de paire de clés RSA (JAVA)
(Mémo) Java pour instruction
Les débutants jouent à des jeux Janken en Java
[Pour les débutants] Exécutez Selenium sur Java
Chiffrer à l'aide du chiffrement RSA en Java
Paramètres de débogage SSL dans Java
Carte en double triée par clé en Java
Premiers pas pour l'apprentissage profond en Java
Organisation des notes dans la tête (Java-Arrangement)
Mémo pour la migration de Java vers Kotlin
Afficher la boîte de dialogue de message avec java (mémo personnel)
Mémo Java
ChatWork4j pour l'utilisation de l'API ChatWork en Java
Techniques de lecture du code source Java dans Eclipse
La solution pour NetBeans 8.2 ne fonctionne pas dans l'environnement Java 9
Organisation des notes dans la tête (Java - édition d'instance)
SourceMapPathOverrides paramètre mémo dans le débogueur VSCode pour Chrome
Mémo organisé dans la tête (Java - type de données)
Définissez un affichage contextuel pour le langage Java avec vim.
Générer du code client Java pour l'API SOAP de SalesForce
Générer OffsetDateTime à partir de Clock et LocalDateTime en Java
Comparez la sortie PDF en Java pour les tests d'instantanés
Activer / désactiver SNI en Java pour chaque communication
Points à connaître avec Java Equals