File: aes_gcm.rb

package info (click to toggle)
ruby-lockbox 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 224 kB
  • sloc: ruby: 1,447; makefile: 4
file content (79 lines) | stat: -rw-r--r-- 2,303 bytes parent folder | download | duplicates (3)
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
module Lockbox
  class AES_GCM
    def initialize(key)
      raise ArgumentError, "Key must be 32 bytes" unless key && key.bytesize == 32
      raise ArgumentError, "Key must be binary" unless key.encoding == Encoding::BINARY

      @key = key
    end

    def encrypt(nonce, message, associated_data)
      cipher = OpenSSL::Cipher.new("aes-256-gcm")
      # do not change order of operations
      cipher.encrypt
      cipher.key = @key
      cipher.iv = nonce
      # From Ruby 2.5.3 OpenSSL::Cipher docs:
      # If no associated data shall be used, this method must still be called with a value of ""
      # In encryption mode, it must be set after calling #encrypt and setting #key= and #iv=
      cipher.auth_data = associated_data || ""

      ciphertext = String.new
      ciphertext << cipher.update(message) unless message.empty?
      ciphertext << cipher.final
      ciphertext << cipher.auth_tag
      ciphertext
    end

    def decrypt(nonce, ciphertext, associated_data)
      auth_tag, ciphertext = extract_auth_tag(ciphertext.to_s)

      fail_decryption if nonce.to_s.bytesize != nonce_bytes
      fail_decryption if auth_tag.to_s.bytesize != auth_tag_bytes

      cipher = OpenSSL::Cipher.new("aes-256-gcm")
      # do not change order of operations
      cipher.decrypt
      cipher.key = @key
      cipher.iv = nonce
      cipher.auth_tag = auth_tag
      # From Ruby 2.5.3 OpenSSL::Cipher docs:
      # If no associated data shall be used, this method must still be called with a value of ""
      # When decrypting, set it only after calling #decrypt, #key=, #iv= and #auth_tag= first.
      cipher.auth_data = associated_data || ""

      begin
        message = String.new
        message << cipher.update(ciphertext) unless ciphertext.to_s.empty?
        message << cipher.final
        message
      rescue OpenSSL::Cipher::CipherError
        fail_decryption
      end
    end

    def nonce_bytes
      12
    end

    # protect key
    def inspect
      to_s
    end

    private

    def auth_tag_bytes
      16
    end

    def extract_auth_tag(bytes)
      auth_tag = bytes.slice(-auth_tag_bytes..-1)
      [auth_tag, bytes.slice(0, bytes.bytesize - auth_tag_bytes)]
    end

    def fail_decryption
      raise DecryptionError, "Decryption failed"
    end
  end
end