File: box.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 (98 lines) | stat: -rw-r--r-- 3,888 bytes parent folder | download | duplicates (2)
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
module Lockbox
  class Box
    NOT_SET = Object.new

    def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil, padding: false, associated_data: nil)
      raise ArgumentError, "Cannot pass both key and encryption/decryption key" if key && (encryption_key || decryption_key)

      key = Lockbox::Utils.decode_key(key) if key
      encryption_key = Lockbox::Utils.decode_key(encryption_key, size: 64) if encryption_key
      decryption_key = Lockbox::Utils.decode_key(decryption_key, size: 64) if decryption_key

      algorithm ||= "aes-gcm"

      case algorithm
      when "aes-gcm"
        raise ArgumentError, "Missing key" unless key
        @box = AES_GCM.new(key)
      when "xchacha20"
        raise ArgumentError, "Missing key" unless key
        require "rbnacl"
        @box = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
      when "xsalsa20"
        raise ArgumentError, "Missing key" unless key
        require "rbnacl"
        @box = RbNaCl::SecretBoxes::XSalsa20Poly1305.new(key)
      when "hybrid"
        raise ArgumentError, "Missing key" unless encryption_key || decryption_key
        require "rbnacl"
        @encryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(encryption_key.slice(0, 32), encryption_key.slice(32..-1)) if encryption_key
        @decryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(decryption_key.slice(32..-1), decryption_key.slice(0, 32)) if decryption_key
      else
        raise ArgumentError, "Unknown algorithm: #{algorithm}"
      end

      @algorithm = algorithm
      @padding = padding == true ? 16 : padding
      @associated_data = associated_data
    end

    def encrypt(message, associated_data: NOT_SET)
      associated_data = @associated_data if associated_data == NOT_SET
      message = Lockbox.pad(message, size: @padding) if @padding
      case @algorithm
      when "hybrid"
        raise ArgumentError, "No encryption key set" unless defined?(@encryption_box)
        raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
        nonce = generate_nonce(@encryption_box)
        ciphertext = @encryption_box.encrypt(nonce, message)
      when "xsalsa20"
        raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
        nonce = generate_nonce(@box)
        ciphertext = @box.encrypt(nonce, message)
      else
        nonce = generate_nonce(@box)
        ciphertext = @box.encrypt(nonce, message, associated_data)
      end
      nonce + ciphertext
    end

    def decrypt(ciphertext, associated_data: NOT_SET)
      associated_data = @associated_data if associated_data == NOT_SET
      message =
        case @algorithm
        when "hybrid"
          raise ArgumentError, "No decryption key set" unless defined?(@decryption_box)
          raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
          nonce, ciphertext = extract_nonce(@decryption_box, ciphertext)
          @decryption_box.decrypt(nonce, ciphertext)
        when "xsalsa20"
          raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
          nonce, ciphertext = extract_nonce(@box, ciphertext)
          @box.decrypt(nonce, ciphertext)
        else
          nonce, ciphertext = extract_nonce(@box, ciphertext)
          @box.decrypt(nonce, ciphertext, associated_data)
        end
      message = Lockbox.unpad!(message, size: @padding) if @padding
      message
    end

    # protect key for xsalsa20, xchacha20, and hybrid
    def inspect
      to_s
    end

    private

    def generate_nonce(box)
      SecureRandom.random_bytes(box.nonce_bytes)
    end

    def extract_nonce(box, bytes)
      nonce_bytes = box.nonce_bytes
      nonce = bytes.slice(0, nonce_bytes)
      [nonce, bytes.slice(nonce_bytes..-1)]
    end
  end
end