0%

erlang使用rsa实现sign/verify

最近需要erlang使用rsa实现签名验证
在网上搜了好久终于拼接成一份完整的代码

rsa常用的格式

私钥

  • 格式1
    1
    2
    3
    4
    5
    6
    -----BEGIN PRIVATE KEY-----
    MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDDxRpuKIdYQ3lR
    ...
    mXCNDx5bSbbPL+WMY8c7F7K0krOfl8Vtzg5cCfo45+IsLCM16sDT0MhLCPUMWj0k
    cd4bKcAc3CENaL3U03oLE58r
    -----END PRIVATE KEY-----
  • 格式2
    1
    MIIEvgI...0kcd4bKcAc3CENaL3U03oLE58r

公钥

  • 格式1
    1
    2
    3
    4
    5
    6
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw8UabiiHWEN5UR5SByS2
    ...
    oK5jmjNnFsCrWbkd68H6tsm9dGPcUMgNWxeahU/8tSOtX94AaTfU4cp846Yn7dH4
    JQIDAQAB
    -----END PUBLIC KEY-----
  • 格式2
    1
    ssh-rsa MIIEvgI...0kcd4bKcAc3CENaL3U03oLE58r

贴代码

简单逻辑就是将各种格式的key转换如下格式:

1
2
3
-----BEGIN XXXX KEY-----
...
-----END XXXX KEY-----

然后调用public_key:pem_decode/1, public_key:pem_entry_decode/1加载key
若是从目录下加载,目录下的key名字必须是ssh_host_rsa_key,ssh_host_rsa_key.pub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
-module(rsa_sign_verify).

-include_lib("public_key/include/public_key.hrl").

-export([sign/2, verify/3]).

ttt() ->
Message = <<"1">>,
Sign1 = sign(Message, {dir, "d:/Downloads/x7mall_test"}),
io:format("sign1:~p~n", [Sign1]),
Verify1 = verify(Message, Sign1, {dir, "d:/Downloads/x7mall_test"}),
io:format("verify1:~p~n", [Verify1]),

Sign2 = sign(Message, "d:/Downloads/x7mall_test_key"),
io:format("sign2:~p~n", [Sign2]),
Verify2 = verify(Message, Sign2, "d:/Downloads/x7mall_test_key.pub"),
io:format("verify2:~p~n", [Verify2]),

Sign3 = sign(Message, {dir, "d:/Downloads/x7mall_test"}),
io:format("sign3:~p~n", [Sign3]),
Verify3 = verify(Message, Sign3, "d:/Downloads/x7mall_test/ssh_host_rsa_key.pub"),
io:format("verify3:~p~n", [Verify3]),

Sign4 = sign(Message, "d:/Downloads/x7mall_test/ssh_host_rsa_key"),
io:format("sign4:~p~n", [Sign4]),
Verify4 = verify(Message, Sign4, {dir, "d:/Downloads/x7mall_test"}),
io:format("verify4:~p~n", [Verify4]),

Sign5 = sign(Message, "d:/Downloads/x7mall_test/ssh_host_rsa_key"),
io:format("sign5:~p~n", [Sign5]),
Verify5 = verify(Message, Sign5, "d:/Downloads/x7mall_test/ssh_host_rsa_key.pub"),
io:format("verify5:~p~n", [Verify5]),
ok.

%% 签名
sign(Message, PrivateKeyFile) ->
PrivateKey = load_private_key(PrivateKeyFile),
Sign = public_key:sign(Message, sha256, PrivateKey),
base64:encode(Sign).

%% 验证
verify(Message, Sign, PublicKeyFile) ->
Sign1 = base64:decode(Sign),
PublicKey = load_public_key(PublicKeyFile),
public_key:verify(Message, sha256, Sign1, PublicKey).

%% 加载private key
load_private_key({file, File}) ->
%% private key格式1
%% -----BEGIN PRIVATE KEY-----
%% MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDDxRpuKIdYQ3lR
%% ...
%% mXCNDx5bSbbPL+WMY8c7F7K0krOfl8Vtzg5cCfo45+IsLCM16sDT0MhLCPUMWj0k
%% cd4bKcAc3CENaL3U03oLE58r
%% -----END PRIVATE KEY-----
%%
%% private key格式2
%% MIIEvgI...0kcd4bKcAc3CENaL3U03oLE58r
%%
{ok, Data} = file:read_file(File),
load_private_key({binary, Data});

load_private_key({binary, Data}) ->
PrivateData =
case re:run(Data, "^-----BEGIN") of
{match, _} -> Data;
_ ->
<<"-----BEGIN PRIVATE KEY-----\n", (format_data(Data))/binary, "\n-----END PRIVATE KEY-----">>
end,
[PrivatePEMEntry | _] = public_key:pem_decode(PrivateData),
#'RSAPrivateKey'{} = PrivateKey = public_key:pem_entry_decode(PrivatePEMEntry),
PrivateKey;
load_private_key({dir, Dir}) ->
%% 目录下文件名字:
%% ssh_host_rsa_key
%% ssh_host_rsa_key.pub
%%
%% private key格式
%% -----BEGIN PRIVATE KEY-----
%% MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDDxRpuKIdYQ3lR
%% ...
%% mXCNDx5bSbbPL+WMY8c7F7K0krOfl8Vtzg5cCfo45+IsLCM16sDT0MhLCPUMWj0k
%% cd4bKcAc3CENaL3U03oLE58r
%% -----END PRIVATE KEY-----
%%
%% public key格式
%% ssh-rsa MIIEvgI...0kcd4bKcAc3CENaL3U03oLE58r
%%
Algorithm = 'ssh-rsa',
Options = [{system_dir, Dir}],
{ok, PrivateKey} = ssh_file:host_key(Algorithm, Options),
PrivateKey;
load_private_key(File) ->
case filelib:is_dir(File) of
true -> load_private_key({dir, File});
false -> load_private_key({file, File})
end.

%% 加载public key
load_public_key({file, File}) ->
%% public key格式1
%% -----BEGIN PUBLIC KEY-----
%% MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw8UabiiHWEN5UR5SByS2
%% ...
%% oK5jmjNnFsCrWbkd68H6tsm9dGPcUMgNWxeahU/8tSOtX94AaTfU4cp846Yn7dH4
%% JQIDAQAB
%% -----END PUBLIC KEY-----
%%
%% public key格式2
%% MIIBIjA...AaTfU4cp846Yn7dH4JQIDAQAB
%%
{ok, Data} = file:read_file(File),
load_public_key({binary, Data});

load_public_key({binary, Data}) ->
PublicData =
case re:run(Data, "^-----BEGIN") of
{match, _} -> Data;
_ ->
NewData =
case re:split(Data, " ") of
[<<"ssh-rsa">>, Data1|_] -> Data1;
_ -> Data
end,
<<"-----BEGIN PUBLIC KEY-----\n", (format_data(NewData))/binary, "\n-----END PUBLIC KEY-----">>
end,
[PublicPEMEntry | _] = public_key:pem_decode(PublicData),
#'RSAPublicKey'{} = PublicKey = public_key:pem_entry_decode(PublicPEMEntry),
PublicKey;
load_public_key({dir, Dir}) ->
%% 同 load_private_key({dir, Dir})
{ok, PublicData} = file:read_file(Dir ++ "/ssh_host_rsa_key.pub"),
[<<"ssh-rsa">>, Data|_] = re:split(PublicData, " "),
load_public_key({binary, Data});

load_public_key(File) ->
case filelib:is_dir(File) of
true -> load_public_key({dir, File});
false -> load_public_key({file, File})
end.

%% 其实不需要格式化也是ok的
%% 主要是为了debug时打印好看
format_data(Data) when is_binary(Data) ->
List = erlang:binary_to_list(Data),
format_data(List);
format_data(List) when is_list(List) ->
NewList = format_data(List, []),
NewList1 = lists:reverse(NewList),
NewList2 = string:join(NewList1, "\n"),
Binary = erlang:list_to_binary(NewList2),
Binary.

format_data(List, NewList) ->
case length(List) > 64 of
true ->
{List1, List2} = lists:split(64, List),
format_data(List2, [List1|NewList]);
false ->
[List|NewList]
end.

参考文档

  1. https://github.com/potatosalad/erlang-crypto_rsassa_pss
  2. https://elixirforum.com/t/verifying-web-crypto-signatures-in-erlang-elixir/20727
  3. https://blog.csdn.net/huangshihaont/article/details/82849920