-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
cipher.cr
167 lines (141 loc) · 4.06 KB
/
cipher.cr
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
162
163
164
165
166
167
require "random/secure"
require "openssl"
# A class which can be used to encrypt and decrypt data using a specified cipher.
#
# ```
# require "random/secure"
#
# key = Random::Secure.random_bytes(64) # You can also use OpenSSL::Cipher#random_key to do this same thing
# iv = Random::Secure.random_bytes(32) # You can also use OpenSSL::Cipher#random_iv to do this same thing
#
# def encrypt(data)
# cipher = OpenSSL::Cipher.new("aes-256-cbc")
# cipher.encrypt
# cipher.key = key
# cipher.iv = iv
#
# io = IO::Memory.new
# io.write(cipher.update(data))
# io.write(cipher.final)
# io.rewind
#
# io.to_slice
# end
#
# def decrypt(data)
# cipher = OpenSSL::Cipher.new("aes-256-cbc")
# cipher.decrypt
# cipher.key = key
# cipher.iv = iv
#
# io = IO::Memory.new
# io.write(cipher.update(data))
# io.write(cipher.final)
# io.rewind
#
# io.gets_to_end
# end
# ```
class OpenSSL::Cipher
class Error < OpenSSL::Error
end
def initialize(name)
cipher = LibCrypto.evp_get_cipherbyname name
raise ArgumentError.new "Unsupported cipher algorithm #{name.inspect}" unless cipher
@ctx = LibCrypto.evp_cipher_ctx_new
# The EVP which has EVP_CIPH_RAND_KEY flag (such as DES3) allows
# uninitialized key, but other EVPs (such as AES) does not allow it.
# Calling EVP_CipherUpdate() without initializing key causes SEGV so
# we set the data filled with "\0" as the key by default.
cipherinit cipher: cipher, key: "\0" * LibCrypto::EVP_MAX_KEY_LENGTH
end
# Sets this cipher to encryption mode.
def encrypt : Nil
cipherinit enc: 1
end
# Sets this cipher to decryption mode.
def decrypt : Nil
cipherinit enc: 0
end
def key=(key)
raise ArgumentError.new "Key length too short: wanted #{key_len}, got #{key.bytesize}" if key.bytesize < key_len
cipherinit key: key
key
end
def iv=(iv)
raise ArgumentError.new "iv length too short: wanted #{iv_len}, got #{iv.bytesize}" if iv.bytesize < iv_len
cipherinit iv: iv
iv
end
# Sets the key using `Random::Secure`.
def random_key
key = Random::Secure.random_bytes key_len
self.key = key
end
# Sets the iv using `Random::Secure`.
def random_iv
iv = Random::Secure.random_bytes iv_len
self.iv = iv
end
# Resets the encrypt/decrypt mode.
def reset
cipherinit
end
# Add the data to be encrypted or decrypted to this cipher's buffer.
def update(data)
slice = data.to_slice
buffer_length = slice.size + block_size
buffer = Bytes.new(buffer_length)
if LibCrypto.evp_cipherupdate(@ctx, buffer, pointerof(buffer_length), slice, slice.size) != 1
raise Error.new "EVP_CipherUpdate"
end
buffer[0, buffer_length]
end
# Outputs the decrypted or encrypted buffer.
def final : Bytes
buffer_length = block_size
buffer = Bytes.new(buffer_length)
if LibCrypto.evp_cipherfinal_ex(@ctx, buffer, pointerof(buffer_length)) != 1
raise Error.new "EVP_CipherFinal_ex"
end
buffer[0, buffer_length]
end
def padding=(pad : Bool)
if LibCrypto.evp_cipher_ctx_set_padding(@ctx, pad ? 1 : 0) != 1
raise Error.new "EVP_CIPHER_CTX_set_padding"
end
pad
end
def name : String
nid = LibCrypto.evp_cipher_nid cipher
sn = LibCrypto.obj_nid2sn nid
String.new sn
end
def block_size : Int32
LibCrypto.evp_cipher_block_size cipher
end
# How many bytes the key should be.
def key_len : Int32
LibCrypto.evp_cipher_key_length cipher
end
# How many bytes the iv should be.
def iv_len : Int32
LibCrypto.evp_cipher_iv_length cipher
end
def finalize
LibCrypto.evp_cipher_ctx_free(@ctx) if @ctx
@ctx = typeof(@ctx).null
end
def authenticated? : Bool
LibCrypto.evp_cipher_flags(cipher).includes?(LibCrypto::CipherFlags::EVP_CIPH_FLAG_AEAD_CIPHER)
end
private def cipherinit(cipher = nil, engine = nil, key = nil, iv = nil, enc = -1)
if LibCrypto.evp_cipherinit_ex(@ctx, cipher, engine, key, iv, enc) != 1
raise Error.new "EVP_CipherInit_ex"
end
nil
end
private def cipher
LibCrypto.evp_cipher_ctx_cipher @ctx
end
end