Skip to content

Commit cf197b4

Browse files
committed
Mirage_crypto.Block.CBC now has {de,en}crypt_into functionality
This may avoid buffer allocations. There are as well unsafe functions for those feeling bounds checks are unnecessary.
1 parent 22f9ff8 commit cf197b4

File tree

3 files changed

+161
-32
lines changed

3 files changed

+161
-32
lines changed

bench/speed.ml

+24-2
Original file line numberDiff line numberDiff line change
@@ -373,12 +373,34 @@ let benchmarks = [
373373
bm "aes-128-cbc-e" (fun name ->
374374
let key = AES.CBC.of_secret (Mirage_crypto_rng.generate 16)
375375
and iv = Mirage_crypto_rng.generate 16 in
376-
throughput name (fun cs -> AES.CBC.encrypt ~key ~iv cs)) ;
376+
throughput_into name
377+
(fun dst cs -> AES.CBC.encrypt_into ~key ~iv cs ~src_off:0 dst ~dst_off:0 (String.length cs))) ;
378+
379+
bm "aes-128-cbc-e-unsafe" (fun name ->
380+
let key = AES.CBC.of_secret (Mirage_crypto_rng.generate 16)
381+
and iv = Mirage_crypto_rng.generate 16 in
382+
throughput_into name
383+
(fun dst cs -> AES.CBC.unsafe_encrypt_into ~key ~iv cs ~src_off:0 dst ~dst_off:0 (String.length cs))) ;
384+
385+
bm "aes-128-cbc-e-unsafe-inplace" (fun name ->
386+
let key = AES.CBC.of_secret (Mirage_crypto_rng.generate 16)
387+
and iv = Mirage_crypto_rng.generate 16 in
388+
throughput name
389+
(fun cs ->
390+
let b = Bytes.unsafe_of_string cs in
391+
AES.CBC.unsafe_encrypt_into_inplace ~key ~iv b ~dst_off:0 (String.length cs))) ;
377392

378393
bm "aes-128-cbc-d" (fun name ->
379394
let key = AES.CBC.of_secret (Mirage_crypto_rng.generate 16)
380395
and iv = Mirage_crypto_rng.generate 16 in
381-
throughput name (fun cs -> AES.CBC.decrypt ~key ~iv cs)) ;
396+
throughput_into name
397+
(fun dst cs -> AES.CBC.decrypt_into ~key ~iv cs ~src_off:0 dst ~dst_off:0 (String.length cs))) ;
398+
399+
bm "aes-128-cbc-d-unsafe" (fun name ->
400+
let key = AES.CBC.of_secret (Mirage_crypto_rng.generate 16)
401+
and iv = Mirage_crypto_rng.generate 16 in
402+
throughput_into name
403+
(fun dst cs -> AES.CBC.unsafe_decrypt_into ~key ~iv cs ~src_off:0 dst ~dst_off:0 (String.length cs))) ;
382404

383405
bm "aes-128-ctr" (fun name ->
384406
let key = Mirage_crypto_rng.generate 16 |> AES.CTR.of_secret

src/cipher_block.ml

+68-25
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,19 @@ module Block = struct
4444

4545
val encrypt : key:key -> iv:string -> string -> string
4646
val decrypt : key:key -> iv:string -> string -> string
47-
val next_iv : iv:string -> string -> string
47+
val next_iv : ?off:int -> string -> iv:string -> string
48+
49+
val encrypt_into : key:key -> iv:string -> string -> src_off:int ->
50+
bytes -> dst_off:int -> int -> unit
51+
val decrypt_into : key:key -> iv:string -> string -> src_off:int ->
52+
bytes -> dst_off:int -> int -> unit
53+
54+
val unsafe_encrypt_into : key:key -> iv:string -> string -> src_off:int ->
55+
bytes -> dst_off:int -> int -> unit
56+
val unsafe_decrypt_into : key:key -> iv:string -> string -> src_off:int ->
57+
bytes -> dst_off:int -> int -> unit
58+
val unsafe_encrypt_into_inplace : key:key -> iv:string ->
59+
bytes -> dst_off:int -> int -> unit
4860
end
4961

5062
module type CTR = sig
@@ -187,40 +199,71 @@ module Modes = struct
187199

188200
let of_secret = Core.of_secret
189201

190-
let bounds_check ~iv cs =
191-
if String.length iv <> block then invalid_arg "CBC: IV length %u" (String.length iv);
192-
if String.length cs mod block <> 0 then
193-
invalid_arg "CBC: argument length %u" (String.length cs)
202+
let bounds_check ?(off = 0) ~iv cs =
203+
if String.length iv <> block then
204+
invalid_arg "CBC: IV length %u not of block size" (String.length iv);
205+
if (String.length cs - off) mod block <> 0 then
206+
invalid_arg "CBC: argument length %u (off %u) not of block size"
207+
(String.length cs) off
194208

195-
let next_iv ~iv cs =
196-
bounds_check ~iv cs ;
197-
if String.length cs > 0 then
209+
let next_iv ?(off = 0) cs ~iv =
210+
bounds_check ~iv cs ~off ;
211+
if String.length cs > off then
198212
String.sub cs (String.length cs - block_size) block_size
199213
else iv
200214

201-
let encrypt ~key:(key, _) ~iv src =
202-
bounds_check ~iv src ;
203-
let dst = Bytes.of_string src in
215+
let unsafe_encrypt_into_inplace ~key:(key, _) ~iv dst ~dst_off len =
204216
let rec loop iv iv_i dst_i = function
205-
0 -> ()
206-
| b -> Native.xor_into_bytes iv iv_i dst dst_i block ;
207-
Core.encrypt ~key ~blocks:1 (Bytes.unsafe_to_string dst) dst_i dst dst_i ;
208-
loop (Bytes.unsafe_to_string dst) dst_i (dst_i + block) (b - 1)
217+
| 0 -> ()
218+
| b ->
219+
Native.xor_into_bytes iv iv_i dst dst_i block ;
220+
Core.encrypt ~key ~blocks:1 (Bytes.unsafe_to_string dst) dst_i dst dst_i ;
221+
loop (Bytes.unsafe_to_string dst) dst_i (dst_i + block) (b - 1)
209222
in
210-
loop iv 0 0 (Bytes.length dst / block) ;
223+
loop iv 0 dst_off (len / block)
224+
225+
let unsafe_encrypt_into ~key ~iv src ~src_off dst ~dst_off len =
226+
Bytes.unsafe_blit_string src src_off dst dst_off len;
227+
unsafe_encrypt_into_inplace ~key ~iv dst ~dst_off len
228+
229+
let encrypt_into ~key ~iv src ~src_off dst ~dst_off len =
230+
bounds_check ~off:src_off ~iv src;
231+
if String.length src - src_off < len then
232+
invalid_arg "CBC: src has insufficient length (%u - src_off:%u < len %u)"
233+
(String.length src) src_off len;
234+
if Bytes.length dst - dst_off < len then
235+
invalid_arg "CBC: dst has insufficient length (%u - dst_off:%u < len %u)"
236+
(Bytes.length dst) dst_off len;
237+
unsafe_encrypt_into ~key ~iv src ~src_off dst ~dst_off len
238+
239+
let encrypt ~key ~iv src =
240+
let dst = Bytes.create (String.length src) in
241+
encrypt_into ~key ~iv src ~src_off:0 dst ~dst_off:0 (String.length src);
211242
Bytes.unsafe_to_string dst
212243

213-
let decrypt ~key:(_, key) ~iv src =
214-
bounds_check ~iv src ;
215-
let msg = Bytes.create (String.length src)
216-
and b = String.length src / block in
244+
let unsafe_decrypt_into ~key:(_, key) ~iv src ~src_off dst ~dst_off len =
245+
let b = len / block in
217246
if b > 0 then begin
218-
Core.decrypt ~key ~blocks:b src 0 msg 0 ;
219-
Native.xor_into_bytes iv 0 msg 0 block ;
220-
Native.xor_into_bytes src 0 msg block ((b - 1) * block) ;
221-
end ;
222-
Bytes.unsafe_to_string msg
247+
Core.decrypt ~key ~blocks:b src src_off dst dst_off ;
248+
Native.xor_into_bytes iv 0 dst dst_off block ;
249+
Native.xor_into_bytes src src_off dst (dst_off + block) ((b - 1) * block) ;
250+
end
223251

252+
let decrypt_into ~key ~iv src ~src_off dst ~dst_off len =
253+
bounds_check ~off:src_off ~iv src;
254+
if String.length src - src_off < len then
255+
invalid_arg "CBC: src has insufficient length (%u - src_off:%u < len %u)"
256+
(String.length src) src_off len;
257+
if Bytes.length dst - dst_off < len then
258+
invalid_arg "CBC: dst has insufficient length (%u - dst_off:%u < len %u)"
259+
(Bytes.length dst) dst_off len;
260+
unsafe_decrypt_into ~key ~iv src ~src_off dst ~dst_off len
261+
262+
let decrypt ~key ~iv src =
263+
let len = String.length src in
264+
let msg = Bytes.create len in
265+
decrypt_into ~key ~iv src ~src_off:0 msg ~dst_off:0 len;
266+
Bytes.unsafe_to_string msg
224267
end
225268

226269
module CTR_of (Core : Block.Core) (Ctr : Counters.S) :

src/mirage_crypto.mli

+69-5
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,8 @@ module Block : sig
253253
@raise Invalid_argument if [iv] is not [block_size], or [msg] is not
254254
[k * block_size] long. *)
255255

256-
val next_iv : iv:string -> string -> string
257-
(** [next_iv ~iv ciphertext] is the first [iv] {e following} the
256+
val next_iv : ?off:int -> string -> iv:string -> string
257+
(** [next_iv ~iv ciphertext ~off] is the first [iv] {e following} the
258258
encryption that used [iv] to produce [ciphertext].
259259
260260
For protocols which perform inter-message chaining, this is the [iv]
@@ -266,9 +266,73 @@ module Block : sig
266266
{[encrypt ~iv msg1 || encrypt ~iv:(next_iv ~iv (encrypt ~iv msg1)) msg2
267267
== encrypt ~iv (msg1 || msg2)]}
268268
269-
@raise Invalid_argument if the length of [iv] is not [block_size], or
270-
the length of [ciphertext] is not [k * block_size] for some [k]. *)
271-
end
269+
@raise Invalid_argument if the length of [iv] is not [block_size].
270+
@raise Invalid_argument if the length of [ciphertext] is not a multiple
271+
of [block_size]. *)
272+
273+
val encrypt_into : key:key -> iv:string -> string -> src_off:int ->
274+
bytes -> dst_off:int -> int -> unit
275+
(** [encrypt_into ~key ~iv src ~src_off dst dst_off len] encrypts [len]
276+
octets from [src] starting at [src_off] into [dst] starting at [dst_off].
277+
278+
@raise Invalid_argument if the length of [iv] is not {!block_size}.
279+
@raise Invalid_argument if [len] is not a multiple of {!block_size}.
280+
@raise Invalid_argument if [String.length src - src_off < len].
281+
@raise Invalid_argument if [Bytes.length dst - dst_off < len]. *)
282+
283+
val decrypt_into : key:key -> iv:string -> string -> src_off:int ->
284+
bytes -> dst_off:int -> int -> unit
285+
(** [decrypt_into ~key ~iv src ~src_off dst dst_off len] decrypts [len]
286+
octets from [src] starting at [src_off] into [dst] starting at [dst_off].
287+
288+
@raise Invalid_argument if the length of [iv] is not {!block_size}.
289+
@raise Invalid_argument if [len] is not a multiple of {!block_size}.
290+
@raise Invalid_argument if [String.length src - src_off < len].
291+
@raise Invalid_argument if [Bytes.length dst - dst_off < len]. *)
292+
293+
(**/**)
294+
val unsafe_encrypt_into : key:key -> iv:string -> string -> src_off:int ->
295+
bytes -> dst_off:int -> int -> unit
296+
(** [unsafe_encrypt_into ~key ~iv src ~src_off dst dst_off len] encrypts [len]
297+
octets from [src] starting at [src_off] into [dst] starting at [dst_off].
298+
299+
It is unsafe since buffer lengths are not checks. This may casue memory
300+
issues if an invariant is violated:
301+
{ul
302+
{- the length of [iv] must be {!block_size},}
303+
{- [len] must be a multiple of {!block_size},}
304+
{- [String.length src - src_off >= len],}
305+
{- [Bytes.length dst - dst_off >= len].}} *)
306+
307+
val unsafe_decrypt_into : key:key -> iv:string -> string -> src_off:int ->
308+
bytes -> dst_off:int -> int -> unit
309+
(** [unsafe_decrypt_into ~key ~iv src ~src_off dst dst_off len] decrypts [len]
310+
octets from [src] starting at [src_off] into [dst] starting at [dst_off].
311+
312+
It is unsafe since buffer lengths are not checks. This may casue memory
313+
issues if an invariant is violated:
314+
{ul
315+
{- the length of [iv] must be {!block_size},}
316+
{- [len] must be a multiple of {!block_size},}
317+
{- [String.length src - src_off >= len],}
318+
{- [Bytes.length dst - dst_off >= len].}} *)
319+
320+
val unsafe_encrypt_into_inplace : key:key -> iv:string ->
321+
bytes -> dst_off:int -> int -> unit
322+
(** [unsafe_encrypt_into_inplace ~key ~iv dst dst_off len] encrypts [len]
323+
octets from [dst] starting at [dst_off] into [dst] starting at [dst_off].
324+
325+
The [dst] buffer must contain the message to be encrypted.
326+
327+
It is unsafe since buffer lengths are not checks. This may casue memory
328+
issues if an invariant is violated:
329+
{ul
330+
{- the length of [iv] must be {!block_size},}
331+
{- [len] must be a multiple of {!block_size},}
332+
{- [String.length src - src_off >= len],}
333+
{- [Bytes.length dst - dst_off >= len].}} *)
334+
(**/**)
335+
end
272336

273337
(** {e Counter} mode. *)
274338
module type CTR = sig

0 commit comments

Comments
 (0)