Class: Gibberish::AES::SJCL

Inherits:
Object
  • Object
show all
Defined in:
lib/ext/aes_crypt.rb

Defined Under Namespace

Classes: CipherOptionsError, DecryptionError, Plaintext

Constant Summary collapse

MAX_ITER =
100_000
ALLOWED_MODES =
['ccm', 'gcm']
ALLOWED_KS =
[128, 192, 256]
ALLOWED_TS =
[64, 96, 128]
DEFAULTS =
{
    v:1, iter:100_000, ks:256, ts:96,
    mode:"gcm", adata:"", cipher:"aes", max_iter: MAX_ITER
}

Instance Method Summary collapse

Constructor Details

#initialize(password, opts = {}) ⇒ SJCL

Returns a new instance of SJCL


56
57
58
59
60
# File 'lib/ext/aes_crypt.rb', line 56

def initialize(password, opts={})
  @password = password
  @opts = DEFAULTS.merge(opts)
  check_cipher_options(@opts)
end

Instance Method Details

#check_cipher_options(c_opts) ⇒ Object

Assume the worst


115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/ext/aes_crypt.rb', line 115

def check_cipher_options(c_opts)
  if @opts[:max_iter] < c_opts[:iter]
    # Prevent DOS attacks from high PBKDF iterations
    # You an increase this by passing in opts[:max_iter]
    raise CipherOptionsError.new("Iteration count of #{c_opts[:iter]} exceeds the maximum of #{@opts[:max_iter]}")
  elsif !ALLOWED_MODES.include?(c_opts[:mode])
    raise CipherOptionsError.new("Mode '#{c_opts[:mode]}' not supported")
  elsif !ALLOWED_KS.include?(c_opts[:ks])
    raise CipherOptionsError.new("Keystrength of #{c_opts[:ks]} not supported")
  elsif !ALLOWED_TS.include?(c_opts[:ts])
    raise CipherOptionsError.new("Tag length of #{c_opts[:ts]} not supported")
  elsif c_opts[:iv] && Base64.decode64(c_opts[:iv]).length > 12
    raise CipherOptionsError.new("Initialization vector's greater than 12 bytes are not supported in Ruby.")
  end
end

#decrypt(h) ⇒ Object


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
# File 'lib/ext/aes_crypt.rb', line 83

def decrypt(h)
  begin
    h = JSON.parse(h, {:symbolize_names => true})
  rescue
    raise "Unable to parse JSON of crypted text"
  end
  check_cipher_options(h)
  key = OpenSSL::PKCS5.pbkdf2_hmac(@password, Base64.decode64(h[:salt]), h[:iter], h[:ks]/8, 'SHA256')
  iv = Base64.decode64(h[:iv])
  ct = Base64.decode64(h[:ct])
  tag = ct[ct.length-h[:ts]/8,ct.length]
  ct = ct[0,ct.length-h[:ts]/8]
  cipherMode = "#{h[:cipher]}-#{h[:ks]}-#{h[:mode]}"
  begin
    c = OpenSSL::Cipher.new(cipherMode)
  rescue RuntimeError => e
    raise "OpenSSL error when initializing: #{e.message}"
  end
  c.decrypt
  c.key = key
  c.iv = iv
  c.auth_tag = tag;
  c.auth_data = h[:adata] || ""
  begin
    out = c.update(ct) + c.final();
  rescue OpenSSL::Cipher::CipherError => e
    raise DecryptionError.new();
  end
  return Plaintext.new(out.force_encoding('utf-8'), h[:adata])
end

#encrypt(plaintext, adata = '') ⇒ Object


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ext/aes_crypt.rb', line 62

def encrypt(plaintext, adata='')
  salt = SecureRandom.random_bytes(8)
  iv = SecureRandom.random_bytes(12)
  key = OpenSSL::PKCS5.pbkdf2_hmac(@password, salt, @opts[:iter], @opts[:ks]/8, 'SHA256')
  cipherMode = "#{@opts[:cipher]}-#{@opts[:ks]}-#{@opts[:mode]}"
  c = OpenSSL::Cipher.new(cipherMode)
  c.encrypt
  c.key = key
  c.iv = iv
  c.auth_data = adata
  ct = c.update(plaintext) + c.final
  tag = c.auth_tag(@opts[:ts]/8);
  ct = ct + tag
  out = {
      v: @opts[:v], adata: adata, ks: @opts[:ks], ct: Base64.strict_encode64(ct).encode('utf-8'), ts: tag.length * 8,
      mode: @opts[:mode], cipher: 'aes', iter: @opts[:iter], iv:  Base64.strict_encode64(iv),
      salt: Base64.strict_encode64(salt)
  }
  out.to_json
end