#ssh接続用の公開鍵( ssh-rsa public key )をopensslで使える形式( X.509 PEM )にshellscriptで変換する Mac OS X (Convert ssh-rsa public key to pem.)
ssh-rsa公開鍵からpem公開鍵への変換。 rsaで公開鍵を使って暗号化するためにはopensslを使えばいいんだが、ssh-keygenで作ったssh接続用のrsa公開鍵そのままではうまくいかない。opensslで使える形式に変換する必要がある。 ssh接続用の公開鍵をopensslで使える形式に変換するのはopenssh付属のssh-keygen で
ssh-keygen -e -m PKCS8 -f ~/.ssh/id_rsa.pub >/tmp/x1sfsdpem.pub
こんな感じでできる。はずなんだけど、現在osx標準搭載のopensshのバージョンでは、なんでかうまく変換できない。そのためこの変換をする場合、最新版のopensshをインストールする必要がある。 このスクリプトは__最新版opensslのインストールなしに__ 変換を実現するもの。 使っているコマンドは下のsandboxの所をみる。
やっていること : ssh-rsa の公開鍵を分解して pkcs#1 のバイナリを作り x.509に組み入れてからPEMにする。
「ssh-rsaの公開鍵」
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNckEuuAeiPesx99s/ivWoXQGdJIMKRl0I0HiSLMCdK/dUrXy5ycyy51cEtv0t/AGQCkAxqiYBCWiVhLV/1qpZuONL9UT8cTa4TO749lFVaucxLNN7nvUtbtA4InKqRjsjqK27vCzyWxiMVIMX0jNpD0rPCwkTK2Ja6knCRN7kA2c3UyNmX4IoQ0xqT0vaUNuxtOe9SkmT3DLizDMbYByzJWVgotbZfOu1QbbClpLt/TbDd5l3fcGNsRzT8Cnd8zdvXk5ZsiUDKKhynvA4Tt/LN9LjZgxTyoEYJewYzf51E8gH057A9zXguBTTAiHMgD8xgeGzh4AVEEFJ1ZO6oCft [email protected]
これを 「pem (x.509) の公開鍵」
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzXJBLrgHoj3rMffbP4r1
qF0BnSSDCkZdCNB4kizAnSv3VK18ucnMsudXBLb9LfwBkApAMaomAQlolYS1f9aq
WbjjS/VE/HE2uEzu+PZRVWrnMSzTe571LW7QOCJyqkY7I6itu7ws8lsYjFSDF9Iz
aQ9KzwsJEytiWupJwkTe5ANnN1MjZl+CKENMak9L2lDbsbTnvUpJk9wy4swzG2Ac
syVlYKLW2XzrtUG2wpaS7f02w3eZd33BjbEc0/Ap3fM3b15OWbIlAyiocp7wOE7f
yzfS42YMU8qBGCXsGM3+dRPIB9OewPc14LgU0wIhzIA/MYHhs4eAFRBBSdWTuqAn
7QIDAQAB
-----END PUBLIC KEY-----
[email protected]
こうします
- バイトの切り張りで変換を行う。
- big endianで抜き出し、書き込みできるように。
- (16進数、10進数) >バイナリ は printf をつかう
- とりあえずPKCS#1のほうのnのバイト数はLong Definite、eの長さはただのバイト数と決め打ちする。(つまりSEQUENCEのながさはssh-rsaのほうのeのバイト数+nのバイト数+6バイト)pkcs#1
- _seq_lenの長さは256以上と決め打ちする。pkcs#1
- ssh-rsa > pkcs#1 > x.509 と作っていく。pkcs#1はどうもopensslでは使えないみたい。
- BIT STRINGの長さには、ビットのあまり指定部を含む。x.509
- とにかくASN.1。
- 実用上nはいいけど、eは念のためLong Definiteに対応しておきたい。Long Definiteは0x80のビットが立っている場合のこと。0x82なら直後の2バイトで長さを表すってこと。128未満はそのバイトに直接長さを入れる。。
- _seq_lenの長さは256以上と決め打ちする。とした部分。
- function にもう少し使い方など説明をつける。
- とりあえずコピペした参考資料をなんとかする。
- 10進数を4byte 2byte(ネットワークバイトオーダー)でバイナリに出すfunctionをつくる。
functionのコードをターミナルに貼り付ける。(この時点では何も起きない。) それから下のように sshpubkey2pem コマンドを打つ。
usage:
sshpubkey2pem <ssh-rsa から始まるssh接続用公開鍵文字列 pathではない>
or
sshpubkey2pem $(cat < path to ssh-rsa pub >)
(例)
sshpubkey2pem ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNckEuuAeiPesx99s/ivWoXQGdJIMKRl0I0HiSLMCdK/dUrXy5ycyy51cEtv0t/AGQCkAxqiYBCWiVhLV/1qpZuONL9UT8cTa4TO749lFVaucxLNN7nvUtbtA4InKqRjsjqK27vCzyWxiMVIMX0jNpD0rPCwkTK2Ja6knCRN7kA2c3UyNmX4IoQ0xqT0vaUNuxtOe9SkmT3DLizDMbYByzJWVgotbZfOu1QbbClpLt/TbDd5l3fcGNsRzT8Cnd8zdvXk5ZsiUDKKhynvA4Tt/LN9LjZgxTyoEYJewYzf51E8gH057A9zXguBTTAiHMgD8xgeGzh4AVEEFJ1ZO6oCft [email protected]
sshpubkey2pem $(cat ~/.ssh/id_rsa.pub)
* ssh接続用公開鍵は ~/.ssh/id_rsa.pub の中身をもらうか、https://github.com/<ユーザー名>.keys
から取得しておく。
result:
-----BEGIN PUBLIC KEY-----
MII........................
....
-----END PUBLIC KEY-----
#shell script で ssh-rsa 形式の公開鍵を opensslで使える形式、x509のPEMにする。
#エディターの都合上インデントなしで勘弁。
#usageもまだ。sshpubkey2pem $(cat ~/.ssh/id_rsa.pub ) こんな感じでつかって!
function sshpubkey2pem ()
{
(
#functions
{
function bit_to_decimal ()
{
echo "obase=10; ibase=2; $1" | bc
}
function decimal_to_bit ()
{
echo "obase=2; ibase=10; $1" | bc
}
function decimal_to_bin_2byte ()
{
(
#単純な方が見やすいのでこれでいいや。
_num="$1"
printf "%b" "\x$(printf '%04x' $_num | cut -b1-2)"
printf "%b" "\x$(printf '%04x' $_num | cut -b3-4)"
)
}
function decimal_to_bin_4byte ()
{
(
_num="$1"
printf "%b" "\x$(printf '%08x' $_num | cut -b1-2)"
printf "%b" "\x$(printf '%08x' $_num | cut -b3-4)"
printf "%b" "\x$(printf '%08x' $_num | cut -b5-6)"
printf "%b" "\x$(printf '%08x' $_num | cut -b7-8)"
)
}
function decimal_to_hex ()
{
(
_num="$1"
_hex=$(printf '%x' $_num)
_hex_length=${#_hex}
if test $(expr $_hex_length % 2 ) -eq 1
then
_hex_length=$(expr $_hex_length + 1)
_hex="0$_hex"
fi
echo $_hex
)
}
function decimal_to_bin()
{
(
_num="$1"
_hex=$( decimal_to_hex $_num )
_bytes=$(expr ${#_hex} / 2 )
_pos=1
while test $_pos -le ${#_hex}
do
printf "%b" "\x$(printf $_hex | cut -b${_pos}-$(expr $_pos + 1) )"
_pos=$(expr $_pos + 2)
done
)
}
{
#DER functions
{
#common
function bytes_of_decimal ()
{
(
_num="$1"
_hex=$(decimal_to_hex "$_num")
_bytes=$(expr ${#_hex} / 2 )
echo $_bytes
)
}
function bytes_of_file ()
{
echo $(wc -c "$1" | tr -s " ") | cut -f1 -d " "
}
}
function der_tag ()
{
(
_class="00"
_primitive_or_constructed="$1"
_tag="$2"
#10進で受け取ったタグをビット列に、5桁になるようにパディング
_tag_bit=$( printf "%05s" $( decimal_to_bit $_tag ) )
#ビット列を作る
#Class:7-6 P/C:5 Tag number:4-0
_bits="${_class}${_primitive_or_constructed}${_tag_bit}"
#10進数にしてバイナリに
decimal_to_bin $( bit_to_decimal $_bits )
)
}
function der_tag_primitive ()
{
(
#タグを出力
_primitive_or_constructed=0
_tag="$1"
der_tag "$_primitive_or_constructed" "$_tag"
)
}
function der_tag_constructed ()
{
#SEQUENCE など用
(
#タグを出力
_primitive_or_constructed=1
_tag="$1"
der_tag "$_primitive_or_constructed" "$_tag"
)
}
function der_length ()
{
(
#usage
#der_length <number(length)>
#128未満はそのまま、128以上は8桁目のビットを立てて、長さの長さを指定するバイト数を出力、その後長さ本体を出力。
_num="$1"
if test $_num -lt 128
then
decimal_to_bin $_num
else
# Long Definite
_bytes=$(bytes_of_decimal "$_num")
#長さの指定が128バイト以上かかることは考慮しない。
decimal_to_bin $( expr $_bytes + 128 )
decimal_to_bin "$_num"
fi
)
}
function der_length_value ()
{
#print der tlv's lv
#usage
#der_length_value <filepath to binaryfile of value>
_file="$1"
_length=$(bytes_of_file "$_file")
der_length $_length
cat $_file
}
function der_bitstring ()
{
#bitstringにしたい中身のfilepathを引数に指定します。
_file_bitstring="$1"
_bytes_bitstring=$(bytes_of_file $_file_bitstring)
# tag BIT STRING
der_tag_primitive 3
# 長さ ビットのあまり指定部の分を加算
der_length $(expr $_bytes_bitstring + 1 )
#ビットのあまり指定部
printf "%b" "\x00"
cat $_file_bitstring
}
}
}
PUBKEY="$@"
NUMBER_BYTE_SIZE=4
_head=0
_file_tmp_files=$(mktemp -t convrsa) ; echo $_file_tmp_files >>$_file_tmp_files
_file_binnakami=$(mktemp -t convrsa) ; echo $_file_binnakami >>$_file_tmp_files
_file_e=$(mktemp -t convrsa) ; echo $_file_e >>$_file_tmp_files
_file_n=$(mktemp -t convrsa) ; echo $_file_n >>$_file_tmp_files
_file_pkcs1_bin=$(mktemp -t convrsa) ; echo $_file_pkcs1_bin >>$_file_tmp_files
_file_x509_nakami=$(mktemp -t convrsa) ; echo $_file_x509_nakami >>$_file_tmp_files
_file_x509_bin=$(mktemp -t convrsa) ; echo $_file_x509_bin >>$_file_tmp_files
echo $PUBKEY | cut -d" " -f2 | base64 -D >>$_file_binnakami
_nokori=$(echo $PUBKEY | cut -d" " -f3- )
{
#ssh-rsa 解析
{
#ssh-rsaの文字列の長さ
_type_string_size=$(printf "%d" $( cat $_file_binnakami | dd bs=1 skip=$_head count=$NUMBER_BYTE_SIZE | od -t x1 | head -n1 | sed -e's/^0000000 *\(..\) *\(..\) *\(..\) *\(..\)/0x\1\2\3\4/')
)
_head=$(expr $NUMBER_BYTE_SIZE + $_head)
_head=$(expr $_head + $_type_string_size)
#eのバイト数
_e_length=$(printf "%d" $( cat $_file_binnakami | dd bs=1 skip=$_head count=$NUMBER_BYTE_SIZE | od -t x1 | head -n1 | sed -e's/^0000000 *\(..\) *\(..\) *\(..\) *\(..\)/0x\1\2\3\4/')
)
_head=$(expr $NUMBER_BYTE_SIZE + $_head)
#eの値
cat $_file_binnakami |dd bs=1 skip=$_head count=$_e_length >$_file_e
_head=$(expr $_e_length + $_head)
#nのバイト数
_n_length=$(printf "%d" $( cat $_file_binnakami | dd bs=1 skip=$_head count=$NUMBER_BYTE_SIZE | od -t x1 | head -n1 | cut -d" " -f2- | tr -d " " | sed -e's/^/0x/' )
)
_head=$(expr $NUMBER_BYTE_SIZE + $_head)
#nの値
cat $_file_binnakami | dd bs=1 skip=$_head count=$_n_length >$_file_n
_head=$(expr $_n_length + $_head)
} 2>/dev/null
#############################################
# PKCS1の中身作成
{
_seq_len=$(expr $_n_length + $_e_length + 6)
(
# tag SEQUENCE
#printf "%b" "\x30"
der_tag_constructed 16
#length
der_length $_seq_len
{
#n
#tag Integer
#printf "%b" "\x02"
der_tag_primitive 2
#length and value
der_length_value $_file_n
}
{
#e
#tag Integer
#printf "%b" "\x02"
der_tag_primitive 2
#length and value
der_length_value $_file_e
}
) >$_file_pkcs1_bin
}
#PKCS1部のバイト数
#_bytes_pkcs1=$(echo $(wc -c $_file_pkcs1_bin | tr -s " ") | cut -f1 -d " ")
#x509バイナリ作成
{
#中身
{
# AlgorithmIdentifier
{
# tag SEQUENCE
der_tag_constructed 16
# SEQUENCEの長さ
der_length 13
{
{
# tag OBJECT IDENTIFIER
der_tag_primitive 6
# length
der_length 9
# value
(
for _char in 2a 86 48 86 f7 0d 01 01 01
do
printf "%b" "\x"$_char
done
)
}
{
# tag NULL
der_tag_primitive 5
# length
der_length 0
}
}
}
#pkcs1部をビットストリングに埋め込む
der_bitstring $_file_pkcs1_bin
} >$_file_x509_nakami
#_bytes_x509_nakami=$(echo $(wc -c $_file_x509_nakami | tr -s " ") | cut -f1 -d " ")
#x509バイナリ
{
# tag SEQUENCE
#printf "%b" "\x30"
der_tag_constructed 16
#length and value
der_length_value $_file_x509_nakami
} >$_file_x509_bin
}
#PEMに
{
echo "-----BEGIN PUBLIC KEY-----"
cat $_file_x509_bin | base64 -b 64
echo "-----END PUBLIC KEY-----"
echo "$_nokori"
}
for _file_tmp in $(cat $_file_tmp_files)
do
rm $_file_tmp;
done
}
)
}
一時フォルダなど環境で違うかも。 sshpubkey2pem $(cat ~/.ssh/id_rsa.pub )を動かすのに最低必要な環境を定義してみた。どんなコマンド使われてんのかななどと思ったときに見ると良い。
sandbox-exec -p '
(version 1)
(deny default)
(import "bsd.sb")
(allow file-read*
(regex #"^/Users/.+/.bashrc$")
(regex #"^/Users/.+/.rnd$"))
(allow file*
(literal "/dev/tty")
(regex #"^/Users/.+/.bash_history$")
)
(allow process-fork)
(allow process-exec* (literal "/bin/bash"))
(allow file-read*
(literal "/Users/shigeta/.ssh/id_rsa.pub"))
(allow file*
(regex #"^/private/var/folders/[^/]+/[^/]+/T/convrsa"))
(allow process-exec*
(literal "/bin/cat")
(literal "/bin/dd")
(literal "/bin/expr")
(literal "/bin/rm")
(literal "/usr/bin/base64")
(literal "/usr/bin/cut")
(literal "/usr/bin/head")
(literal "/usr/bin/mktemp")
(literal "/usr/bin/od")
(literal "/usr/bin/sed")
(literal "/usr/bin/tr")
(literal "/usr/bin/wc"))
' /bin/bash
#注意 このスクリプトは暗号解読時に秘密鍵へのアクセスがあります。opensslへの引数。
# ~/.ssh/id_rsa ~/.ssh/id_rsa.pub が対である前提。
(
_file_pem=$(mktemp -t convrsa)
_file_target=$(mktemp -t convrsa)
_file_tmp=$(mktemp -t convrsa)
(
sshpubkey2pem $(cat ~/.ssh/id_rsa.pub ) >$_file_pem
openssl asn1parse -inform PEM -in $_file_pem -dump || {
echo "error"
exit 1
}
openssl rsa -inform PEM -pubin -text -in $_file_pem || {
echo "error"
exit 1
}
echo test >$_file_target
_encryptedstr=$(openssl rsautl -pkcs -encrypt -pubin -inkey $_file_pem < $_file_target >$_file_tmp && cat $_file_tmp | base64)
echo $_encryptedstr
_decryptedstr=$(echo $_encryptedstr | base64 -D | openssl rsautl -decrypt -inkey ~/.ssh/id_rsa ||{
echo "error"
exit 1
}
)
echo $_decryptedstr
if test "$_decryptedstr" != "test"
then
echo error
exit 1
fi
) && echo success
rm $_file_pem $_file_target $_file_tmp
)
test for decimal_to_bin (function decimal_to_hex と function decimal_to_binを貼り付けてから)
(
echo "decimal_to_bin がちゃんと動いてるか"
echo 65536
_val="$(echo $(decimal_to_bin 65536 | od -tx1 | head -n1 ) )"
if test "$_val" == '0000000 01 00 00'
then
echo "success"
else
echo "fail : $_val"
fi
echo 255
_val="$(echo $(decimal_to_bin 255 | od -tx1 | head -n1 ) )"
if test "$_val" == '0000000 ff'
then
echo "success"
else
echo "fail : $_val"
fi
)
test for DER util
(
test $(echo $(der_tag_primitive 16 | od -tx1 | head -n1 |cut -d" " -f2- ) ) -eq 10 || echo error der_tag_primitive 16
test $(echo $(der_tag_constructed 16 | od -tx1 | head -n1 |cut -d" " -f2- ) ) -eq 30 || echo error der_tag_constructed 16
test $(echo $(der_tag_constructed 1 | od -tx1 | head -n1 |cut -d" " -f2- ) ) -eq 21 || echo error der_tag_constructed 16
)
- ssh接続に使っているrsa公開鍵はssh-rsaと言われるopenssh独自のもの。中身が同じで一般的なものはPKCS#1。
- 公開鍵の中にはe(publicExponent)とn(modulus)が入っている。
- ssh-rsaとPKCS#1は入れ物の形が違うが、中に入ってる重要なものは同じ。
- opensslではPKCS#1のままでは使えなくて、x.509にすれば使える。
- x.509のなかにはPKCS#1が入っている。
- x.509はDERで符号化されている。たぶんPKCS#1もそうだと思う。
- DERはBERの符号化方法に制限をかけた符号化方法。
- BERはASN.1で定義されたコード化方法。TLV符号化という技術を使っている。
- TLVは Type,Length,Valueで構成される。入れ子にできる。
- 値はネットワークバイトオーダー。(頭から順番に並んでるやつ)
- DERではBIT STRINGの未使用ビットは常に0。
- TLVのLが128以上の場合Long Definiteをつかう。これはLの0x80のビットを立てて、下7bitに「長さを入れる箱」の長さを指定する。Lが0x82ならLong Definiteで直後の2バイトに長さが入っているという意味。0x03ならそのままVの長さは3バイト。
- PEMはx.509とかpkcs#1とかのバイナリをbase64デコードで64バイトずつ改行しながら出力したものをヘッダ、フッター(-----BEGIN PUBLIC KEY-----など)ではさむ。
ASN.1 Table 1 – Universal class tag assignments
The "ssh-rsa" key format has the following specific encoding:
string "ssh-rsa"
mpint e
mpint n
Here the 'e' and 'n' parameters form the signature key blob.
mpint
Represents multiple precision integers in two's complement format,
stored as a string, 8 bits per byte, MSB first. Negative numbers
have the value 1 as the most significant bit of the first byte of
the data partition. If the most significant bit would be set for
a positive number, the number MUST be preceded by a zero byte.
Unnecessary leading bytes with the value 0 or 255 MUST NOT be
included. The value zero MUST be stored as a string with zero
bytes of data.
なので、正の整数を入れたいときに、最初のバイトがff,a0など一番頭のビットが立ちそうなら、00のバイトを頭にくっつけて負の数になるのを防ぐって感じだと思う。ff > 00 ff ,a0 > 00 a0
binary 意味
00 00 00 07 文字列 "ssh-rsa"の長さ
73 73 68 2d 72 73 61 "ssh-rsa"
00 00 00 03 数値のバイト数(3)
01 00 01 eの値(0x10001)
00 00 01 01 数値のバイト数(0x101)
00 ef 3d a8... nの値(0xEF3DA8...)
PKCS#1
A.1.1 RSA public key syntax
An RSA public key should be represented with the ASN.1 type
RSAPublicKey:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
The fields of type RSAPublicKey have the following meanings:
* modulus is the RSA modulus n.
* publicExponent is the RSA public exponent e.
INTEGERの定義はまだ探していないが、上のssh-rsaのmpintの値をそのまま入れて問題なく動くことは確認した。ので、負の整数になりそうな場合の処理は同じなのかなと思う。
binary 意味
30 SEQUENCE
82 次の2バイトがSEQUENCEの長さ
01 0a 0x10Aバイト
02 INTEGER
82 次の2バイトがINTEGERの長さ
01 01 nの長さ(0x101バイト)
00 ef 3d a8... nの値(0xEF3DA8...)
02 INTEGER
03 INTEGERの長さ(3バイト)
01 00 01 eの値(0x10001)
つまりSEQUENCEのながさはssh-rsaのほうのssh-rsaより後ろの部分のの長さ+ 決め打ち nのバイト数はロングレングスエンコーディングeの長さはただのバイト数。 TODO 実用上nはいいけど、eは念のためロングレングスエンコーディングに対応しておきたい。 ロングレングスエンコーディングは0x82の場合のこと。2以外で0x80が立っていたらエラーにするか?
だめだった。rsa public key(pkcs#1)に対応してなかった。(OpenSSL 0.9.8zd 8 Jan 2015) さらにX.509で包む。
binary 意味
30 SEQUENCE
82 次の2バイトがSEQUENCEの長さ
01 22 0x122バイト
30 SEQUENCE 00 1 10000(constructedの16)
0d SEQUENCEの長さ 13バイト
06 OBJECT IDENTIFIER 00 0 0110(6) 中身はAlgorithmIdentifier
09 OBJECT IDENTIFIERの長さ(9バイト)
2a 86 48 86 f7 0d 01 01 01 PKCS#1 rsaEncryption これは決められた値(RFC 3279)。
05 NULL The parameters field MUST have ASN.1 type NULL for this algorithm identifier. (RFC 3279)
00 NULLの長さ(0バイト)
03 BIT STRING
82 次の2バイトがBIT STRINGの長さ
01 0f BIT STRINGの長さ(0x10Fバイト) 未使用ビットの長さ部のバイトを含む
00 未使用ビットの長さ(0ビット)
30 82 01 0a... PKCS#1 公開鍵
5バイト目からが
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
で、rsaのばあいparametersはASN.1のNULLで固定。 30 はSequence and Sequence-of typesの複合型(bit5 (6bit目)あり)