77373


`ssh-keygen` で rsa 鍵を生成すると、その公開鍵は以下のような形式になります。

[code]

    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC+spofrTV64o+qg+X3+zn9hNgQUgh1Q2STKLh9YIhQM72fdfVQfflnsgRUG38E/Bu3fymbJ6i8zIecqoRqVXYN7VBNXagZmx6jMkm90ccCuC8jiti5nEmIMpL8136Bf4xHXiTtYGwzzJoJQf2dTP2A0EwUXlNAFP0WMPDjPkBAOTI7miGrYrkR7E9lwpx3pb6KHsQx9kTiTbxx2nd+88EUNYUFKhNaXnF7O0ic0rHTNgJUISJ7fnwAvdbJUcPcwO5YBPNmKi0J3mu4VS/g9OP+2U1KbTzXmkZtKWXoi/EdivbvLhw6I82AEnwRyw/KSSAkbBV0i7xyxJMH/5IUnzdzWH7IQpfLXke0VRGwBtbQ0bsGSoS6zOe4wkiPEA64jxcKF5iaZGA8dHTZsPQSjduQovlEfwbnW2brIm7jEbJTZRZDLdbHeWXQEUY1aC6Mh8pKmVB8Oepsog7wJ/57rKospDgR6Fzejp3yfi8gZbcDdUardlvDvWRc9Rf1/ULwrvM= comment
    
[/code]

ssh の許可をアクセスする際には、この公開鍵を `authorized_keys`
などに登録していくことになりますが、ここで登録された鍵を眺めていると、この鍵はどの種類の rsa ssh 鍵なのか、というのを判定したくなったりします。

具体的には、既に deprecated な rsa 鍵が authorized_keys に設定されていないか?を確認したいときなどです。

# 質問

openssh の公開鍵の内容をパース、ないし、その鍵の種類の判定を行なう方法は、どのようなものがありますか?


CC BY-SA 4.0
2021-01-18T06:31:09.070
73378
2021-06-07T12:46:55.937
2021-01-18T07:33:02.830
3060
754
question
ssh openssh
ssh-rsa な pubkey をパースないし、どのような鍵であるか判定したい


`ssh-keygen` の `-l` オプションでフィンガープリントを表示できますが、この結果に鍵の種類や長さも表示されるようです。

**CentOS 7.9 での実行結果:**

[code]

    $ ssh-keygen -l -f ~/.ssh/authorized_keys
    4096 SHA256:EFUmCXuF/+nrkRmaBDSch0+DjNuK20EfqPXnnl0bMZc comment (RSA)
    
    $ rpm -q openssh
    openssh-7.4p1-21.el7.x86_64
    
[/code]

ただし、OpenSSH のバージョンによって表示結果は異なるようです。

**RHEL 5.11 での実行結果:**

[code]

    $ ssh-keygen -l -f ~/.ssh/authorized_keys
    2048 d7:ec:dd:68:28:6a:ff:93:80:8c:46:6f:66:2d:bf:fa /root/.ssh/authorized_keys
    
    $ rpm -q openssh
    openssh-4.3p2-82.el5
    
[/code]


CC BY-SA 4.0
2021-01-18T07:07:08.950
73379
2021-01-18T07:07:08.950
3060
73378
answer


まず最初に確認ですが「鍵の種類」というのは具体的には何を知りたいのですか?

sshの鍵の種類というとまずは"RSA鍵"や"Ed25519鍵"というのが思い浮かびます。  
OpenSSHの公開鍵は`鍵種別 鍵本体 コメント`というように空白で区切られた三つの部分に分かれ、この内の鍵種別が`ssh-
rsa`ならばRSA鍵、`ssh-ed25519`ならばEd25519鍵というように簡単に判別が出来ます。

また、鍵本体部分でも判別が可能ですが([参考](https://twitter.com/ttdoda/status/1390990375246909445))、通常は使う必要が無いでしょうし、この部分だけで判断する事は推奨されていません。

> この鍵はどの種類の rsa ssh 鍵なのか、というのを判定したくなったりします。

鍵種別が`ssh-rsa`の鍵はすべてSSH v2用のRSA鍵(raw)であり、RSA鍵の中での種類というのは有りません。  
厳密には公開鍵の形式として`pgp-sign-rsa`というのも有りますが、OpenSSHはこれに対応していないので特に考える必要は無いでしょう。  
また`ssh-rsa-
cert-v01@openssh.com`という証明書を使った認証方式も有りますが、これで使われるのはRSA公開鍵(id_rsa.pub)と認証局による鍵署名(id_rsa.pub.cert)というように二つのファイルに分かれていて、公開鍵ファイル自体は通常のRSA鍵と同じ物です。

> 具体的には、既に deprecated な rsa 鍵が authorized_keys に設定されていないか?を確認したいときなどです。

`deprecated な rsa 鍵`かを確認するには、まずdeprecatedとする基準を決める必要がありますよね。  
RSA鍵の場合は通常は基準として鍵長が用いられますが、鍵長は`ssh-keygen -l`コマンドで表示出来ます。  
また公開鍵ファイルのサイズは鍵長とほぼ比例するので、簡易的でいいならば基準となる鍵長のRSA鍵を生成し、その公開鍵ファイルとサイズを比べるという方法も使えます。  
もう少し厳密に行うならば公開鍵ファイルの鍵本体部分を抜き出す方がいいでしょう。

## 鍵形式と認証方式

質問を読んで感じたのですが、存在しないRSA鍵としての種類を気にしている事から、何か誤解をされているように思います。

もしかしてOpenSSH 8.2以降のリリースノートに書かれている、「ssh-rsa認証(署名)方式("ssh-rsa" public key
signature algorithm)の廃止予告」の事を気にされているのでしょうか?

`ssh-rsa`という名前は、公開鍵形式と認証方式の二種類の事で使われています。  
OpenSSHが廃止予告しているのは認証方式の方のみであり、公開鍵形式は廃止予告の対象ではありません。  
代替方式として最初に挙げられている`rsa-sha2-256`/`rsa-sha2-512`認証方式で使うRSA鍵形式も`ssh-rsa`であり、`ssh-
rsa`認証方式で使っていた鍵がそのまま使えます。(サーバ、およびクライアント共にOpenSSH
7.2以降を使っているのならば、ユーザは意識していなくてもすでにRSA鍵認証では`rsa-sha2-512`認証方式が使われています)

## ssh-rsa公開鍵のパース

上記の事を踏まえた上で、それでも`ssh-rsa`公開鍵をパースしたいという事ならば、形式自体は単純なのでスクリプトが簡単に書けます。

sshの公開鍵データの形式は[RFC4253](https://datatracker.ietf.org/doc/html/rfc4253)の[6.6.
Public Key
Algorithms](https://datatracker.ietf.org/doc/html/rfc4253#section-6.6)に以下のように書かれています。

[code]

          string    "ssh-rsa"
          mpint     e
          mpint     n
    
[/code]

OpenSSHの公開鍵形式の鍵本体部分は、上記データをBase64エンコードした物です。  
公開鍵本体部分には他の情報は含まれていません。  
`string`および`mpint`がどのような形式かは、[RFC
4251](https://datatracker.ietf.org/doc/html/rfc4251)の[5\. Data Type
Representations Used in the SSH
Protocols](https://datatracker.ietf.org/doc/html/rfc4251#section-5)に書かれています。

以下は、rubyで外部ライブラリを何も使わずに書いた例です。

[code]

    class String
      def get_uint32!
        self.slice!(0,4).unpack1("N")
      end
     
      def get_string!
        len = get_uint32!
        self.slice!(0, len)
      end
     
      def get_mpint!(len)
        self.slice!(0, len).unpack("C*").inject do |r, v| (r << 8) + v end
      end
    end
     
    tag, b64key, comment = gets.split(/\s+/, 3)
     
    puts "tag: #{tag}"
    puts "key: #{b64key}"
    puts "comment: #{comment}"
     
    key = b64key.unpack1("m")
    ktag = key.get_string!
     
    puts "pubkey tag: #{ktag}"
    puts "WARNING: tag and pubkey-tag differ" if tag != ktag
    if ktag != "ssh-rsa"
      puts "ERROR: not ssh-rsa key"
      exit 1
    end
     
    elen = key.get_uint32!
    e = key.get_mpint!(elen)
     
    nlen = key.get_uint32!
    keylen = (key[0].ord == 0 ? nlen - 1 : nlen) * 8
    n = key.get_mpint!(nlen)
     
    puts "key length: #{keylen}"
    puts "e: #{e}"
    puts "n: #{n}"
     
    puts "left data: #{key}" unless key.empty?
    
[/code]

実行例:

[code]

    % ./parse-rsa-pubkey.rb testkey.pub
    tag: ssh-rsa
    key: AAAAB3NzaC1yc2EAAAADAQABAAABgQC+spofrTV64o+qg+X3+zn9hNgQUgh1Q2STKLh9YIhQM72fdfVQfflnsgRUG38E/Bu3fymbJ6i8zIecqoRqVXYN7VBNXagZmx6jMkm90ccCuC8jiti5nEmIMpL8136Bf4xHXiTtYGwzzJoJQf2dTP2A0EwUXlNAFP0WMPDjPkBAOTI7miGrYrkR7E9lwpx3pb6KHsQx9kTiTbxx2nd+88EUNYUFKhNaXnF7O0ic0rHTNgJUISJ7fnwAvdbJUcPcwO5YBPNmKi0J3mu4VS/g9OP+2U1KbTzXmkZtKWXoi/EdivbvLhw6I82AEnwRyw/KSSAkbBV0i7xyxJMH/5IUnzdzWH7IQpfLXke0VRGwBtbQ0bsGSoS6zOe4wkiPEA64jxcKF5iaZGA8dHTZsPQSjduQovlEfwbnW2brIm7jEbJTZRZDLdbHeWXQEUY1aC6Mh8pKmVB8Oepsog7wJ/57rKospDgR6Fzejp3yfi8gZbcDdUardlvDvWRc9Rf1/ULwrvM=
    comment: comment
    pubkey tag: ssh-rsa
    key length: 3072
    e: 65537
    n: 4327649583910065021093066394378349622703751517771966283936947382202453578096924014363834082410743933577887832365613732256889143589773614570855198533091428787691202680627513712669799711056798144776908764216417618025419754541295883860149320696601188391542414277071378007740998563573890816715772825721792861629120168905914026851492187603967486133142230182122886563163582466833650721388016726473679277978877655811527332508572613894440801906107651579977754315570612781101975662189796831535512865605736222833316158238719553666355973586756899997461824206420966641678235244389694556205797956129668235218541135398243157295606170063250946665246153910140628140995881528110934412117712819528529601830538115625298103018689126317924676582664729053804281479823154379323914768857746303210150634283208749020083767840352410211349028169020173052358083092122294127932425468490065451247010899507966998125381751141789245914984151508351340069826291
    
[/code]


CC BY-SA 4.0
2021-06-06T19:11:06.430
77373
2021-06-06T19:11:06.430
12203
73378
answer