Module: TcryptoJava::Crypto

Included in:
Tcrypto::Cipher
Defined in:
lib/tcrypto_java/crypto.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.are_files_equal?(opts = { }) ⇒ Boolean

end decrypt()

Returns:

  • (Boolean)


412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/tcrypto_java/crypto.rb', line 412

def Crypto.are_files_equal?(opts = { })
  if not opts.nil? and not opts[:files].nil? and opts[:files].length > 1
    
    equal = true
    
    prevRes = nil
    opts[:files].each do |f|
      # path expected...
      res = Gcrypto::Digest.generate({ file: f })
      if not prevRes.nil?
        if not java.util.Arrays.is_equals(res, preRes)
          equal = false
          break
        end
      end
    end

    equal
   
  else
    # if nothing sent to me or only single file sent to me
    # then must be true since there would be no comparison
    true 
  end
end

.copy_file_to_output(srcFile, output, opts = { }, &block) ⇒ Object

Raises:



472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/tcrypto_java/crypto.rb', line 472

def Crypto.copy_file_to_output(srcFile, output, opts = { }, &block)
  raise TcryptoJava::Error, "Source file cannot be nil or empty" if srcFile.nil? or srcFile.empty?
  raise TcryptoJava::Error, "Output cannot be nil" if output.nil?
 
  if srcFile.is_a?(java.io.InputStream)
    src = srcFile
  elsif srcFile.is_a?(String)
    # assume is file path?
    f = java.io.File.new(srcFile)
    raise TcryptoJava::Error, "Cannot read source file from a directory [#{f.absolute_path}]" if f.is_directory?
    raise TcryptoJava::Error, "Given file doesn't exist [#{f.absolute_path}]" if not f.exists
    src = java.io.FileInputStream.new(f)
  else
    raise TcryptoJava::Error, "Unsupported source file type #{srcFile.class}"
  end

  if output.is_a?(java.io.OutputStream)
    TcryptoJava::GConf.instance.glog.debug "Given output is an OutputStream. Direct use the object."
    os = output
  elsif output.is_a?(String)
    # assume is file path?
    f = java.io.File.new(output)
    raise TcryptoJava::Error, "Cannot write output to a directory [#{f.absolute_path}]" if f.is_directory?
    
    TcryptoJava::GConf.instance.glog.debug "Opening given path #{f.absolute_path} to copy file into"
    os = java.io.FileOutputStream.new(f)
    
  else
    raise TcryptoJava::Error, "Unsupported destination type #{val[:dest].class}"
  end

  bufSize = opts[:bufSize] || 102400 
  b = Java::byte[bufSize].new
  while((read = src.read(b,0,b.length)) != -1)
    if block
      block.call(:before_write, { buf: b, from: 0, len: read })
    end
    
    os.write(b,0,read)  
  end
  
end

.equals?(first, second) ⇒ Boolean

Returns:

  • (Boolean)


439
440
441
442
443
444
445
446
447
448
449
# File 'lib/tcrypto_java/crypto.rb', line 439

def Crypto.equals?(first, second)
  if first.nil? or second.nil?
    true
  else
    begin
      java.util.Arrays.equals(first,second)
    rescue Exception => ex
      raise TcryptoJava::Error, ex
    end
  end
end

.input_length(input) ⇒ Object



462
463
464
465
466
467
468
469
470
# File 'lib/tcrypto_java/crypto.rb', line 462

def Crypto.input_length(input)
  if not input[:file].nil?
    input[:file].length
  elsif not input[:bin].nil?
    input[:bin].length
  else
    raise TcryptoJava::Error, "Unknown input for length extraction #{input}"
  end
end

.load_file_into_memory(file, opts = { }, &block) ⇒ Object

Raises:



515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/tcrypto_java/crypto.rb', line 515

def Crypto.load_file_into_memory(file, opts = { }, &block)
  raise TcryptoJava::Error, "No file given" if file.nil?
  f = java.io.File.new(file)
  raise TcryptoJava::Error, "Given file #{f.absolute_path} does not exist" if not f.exists
  raise TcryptoJava::Error, "Given file #{f.absolute_path} is a directory" if f.directory?
  
  total = f.length
  bufSize = opts[:bufSize] || 102400 
  b = Java::byte[bufSize].new
  baos = java.io.ByteArrayOutputStream.new
  processed = 0
  fis = java.io.FileInputStream.new(f)
  while((read = fis.read(b,0,b.length)) != -1)
    baos.write(b,0,read)
    processed += read
  
    block.call(:progress, { total: total, processed: processed }) if block
  end

  baos.toByteArray
  
end

.to_file(path) ⇒ Object

utilities here is to assist crypto_header encode/decode operations



454
455
456
# File 'lib/tcrypto_java/crypto.rb', line 454

def Crypto.to_file(path)
  IoUtils.to_file(path)      
end

.to_input(buf) ⇒ Object



458
459
460
# File 'lib/tcrypto_java/crypto.rb', line 458

def Crypto.to_input(buf)
  { bin_is: java.io.ByteArrayInputStream.new(buf), bin: buf }
end

Instance Method Details

#decrypt(opts = { }, &block) ⇒ Object

decrypt()

Raises:

  • (Tcrypto::Error)


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/tcrypto_java/crypto.rb', line 222

def decrypt(opts = { }, &block)

  raise Tcrypto::Error, "No input given for tagged decryption" if opts.nil? or opts.empty?
  is = IoUtils.load_input(opts)

  # 
  # read header section from the input
  # 
  header = Tcrypto::CryptoHeaderEngine.decode(is) 
  TcryptoJava::GConf.instance.glog.debug "Header decoded successfully"

  # 
  # Read Section 2...
  #
  headerMisc = Tcrypto::CryptoHeaderEngine.decode(is)
  TcryptoJava::GConf.instance.glog.debug "Header misc decoded successfully"
  # 
  # Done section 2 is in...
  #

  # decryption material
  id = opts[:identity]
  raise Tcrypto::Error, "No decryption material given via key :identity" if id.nil?

  if id.is_a?(Pkernel::Identity)
    idType = :identity
  elsif id.is_a?(Gcrypto::CryptoContext) 
    idType = :secret_key
  elsif id.is_a?(Tcrypto::PasswordRecipient) #(String)
    idType = :password
  else
    raise Tcrypto::Error, "Unsupported decryption material #{id}"
  end

  foundRecp = nil
  header[:recipients].each do |recp|
    if recp[:type] == :crypto_cert_recp and (idType == :identity)
      if id.certificate.nil?
      else
        if Pkernel::Certificate.is_equal?(recp[:cert],id.certificate)
          TcryptoJava::GConf.instance.glog.debug "Given certificate matches cert recp from header"
          foundRecp = recp 
          break
        end
      end
    elsif recp[:type] == :crypto_symkey_recp and (idType == :secret_key)
      TcryptoJava::GConf.instance.glog.debug "Given symkey matches symkey recp from header"
      recp[:crypto_context] = id
      foundRecp = recp
      break
    elsif recp[:type] == :crypto_pass_recp and (idType == :password)
      # decode password recipient!
      begin
        if (id.challenge.nil? or id.challenge.empty?)
          if block
            id.challenge = block.call(:prompt_challenge, { })
          else
            raise Tcrypto::Error, "Challenge is not given. Either pass in via Tcrypto::PasswordRecipient or provide a block which response to key :prompt_challenge"
          end
        end

        recp[:challenge_cc].key = Gcrypto::PBKDF2.derive(id.challenge, recp[:challenge_derive_key])[:key]
        msgId = Gcrypto::SecretKeyCrypto.decrypt({ bin: recp[:challenge_nounce], crypto_context: recp[:challenge_cc] })
        if Tcrypto::TagProvider.to_string(msgId) != header[:message_id]
          raise Tcrypto::Error, "Password recipient challenge failed to be verified!"
        end
      rescue Exception => ex
        if ex.message =~ /Tag mismatch/
          raise Tcrypto::Error, "Password recipient challenge verification failed!"
        else
          raise Tcrypto::Error, ex
        end
      end 

      begin

        TcryptoJava::GConf.instance.glog.debug "Given password matches challenge from header"

        if (id.password.nil? or id.password.empty?)
          if block
            id.password = block.call(:prompt_password, { })
          else
            raise Tcrypto::Error, "Password is not given. Either pass in via Tcrypto::PasswordRecipient or provide a block which response to key :prompt_password"
          end
        end

        derivedKey = Gcrypto::PBKDF2.derive(id.password, recp[:derive_func])
        recp[:crypto_context].key = derivedKey[:key]
        foundRecp = recp
        break
      rescue Exception => ex
        raise Tcrypto::Error, ex
      end
        
    end
  end

  if foundRecp.nil?
    TcryptoJava::GConf.instance.glog.debug "Given identity is not able to decrypt the data or not an intended recipient"
    raise Tcrypto::Error, "Given identity is not able to decrypt the data or not an intended recipient"
  else

    TcryptoJava::GConf.instance.glog.debug "Given identity is capable to decrypt data."

    # Ok, eligiable to decrypt... so decrypt the session key...
    case idType
    when :identity
      keysBin = Gcrypto::KeyPairCrypto.decrypt({ enc_bin: foundRecp[:enc_key], identity: id })
    when :password
      keysBin = Gcrypto::SecretKeyCrypto.decrypt({ bin: foundRecp[:enc_key], crypto_context: foundRecp[:crypto_context] })
    when :secret_key
      keysBin = Gcrypto::SecretKeyCrypto.decrypt({ bin: foundRecp[:enc_key], crypto_context: foundRecp[:crypto_context] })
    else
      raise Tcrypto::Error, "Unsupported data decryption type #{idType}"
    end

    # keysBin has two keys...
    keys = org.bouncycastle.asn1.ASN1InputStream.new(keysBin).readObject.to_a
    # decode the keys
    header[:crypto_context].key = Gcrypto::SecretKey.load({ bin: Tcrypto::TagProvider.value(keys[0]) })
    header[:mac_context].key = Gcrypto::SecretKey.load({ bin: Tcrypto::TagProvider.value(keys[1]) })

    # 
    # read payload section from the input
    #
    Tcrypto::GConf.instance.glog.debug "Loading payload section..."
    payload = Tcrypto::CryptoHeaderEngine.decode(is)

    # 
    # check if the header being changed unauthorised
    #
    if opts[:skip_header_integrity_check].nil? or opts[:skip_header_integrity_check] == false

      if not (header[:bin].nil? and headerMisc[:header_sign].nil?)
        # verify header signature first...
        sign = Gcrypto::SecretKeyCrypto.sign({ bin: header[:bin], crypto_context: header[:crypto_context] })
        if not java.util.Arrays.equals(sign, headerMisc[:header_sign])
          raise Tcrypto::Error, "Header integrity check failed. The header info has been tampered."    
        else
          TcryptoJava::GConf.instance.glog.debug "Header integrity verified!"
        end
      end

    end

    ## got session key... proceed decrypt content!
    begin
      mac = Gcrypto::SecretKeyCrypto.verify_init( { crypto_context: header[:mac_context] } )

      decPara = { crypto_context: header[:crypto_context] , bin: payload[:payload]  }
      decPara[:outFile] = opts[:outFile] if not opts[:outFile].nil?
      
      plain = Gcrypto::SecretKeyCrypto.decrypt(decPara) do |ops, opts|
        if ops == :after_dec
          Gcrypto::SecretKeyCrypto.verify_update(mac, opts)
        end
      end

      if not header[:data_sign].nil?

        match = Gcrypto::SecretKeyCrypto.verify_finalize(mac, { mac: header[:data_sign] })
        TcryptoJava::GConf.instance.glog.debug "Data integrity check status : #{match}"

        if not match and opts[:skip_data_integrity_check] == true
          # remove the generated output if data integrity failed...
          if not (decPara[:outFile].nil? or decPara[:outFile].empty?)
            java.io.File.new(decPara[:outFile]).delete
          end

          plain = ""
          
          raise Tcrypto::Error, "Original data integrity has compromised!"
        end

      end

      if opts[:outFile].nil?
        plain
      end

    rescue Exception => ex
      raise Tcrypto::Error, "Error decrypting payload. Error was: #{ex.message}. Probably incorrect key?"
    end

  end


end

#encrypt(opts = { }) ⇒ Object

encrypt()



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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/tcrypto_java/crypto.rb', line 19

def encrypt(opts = { })
 
  rcpts = opts[:recipients]
  if rcpts.nil? or rcpts.empty?
    raise Tcrypto::Error, "No recipients given to encrypt" 
  end
  
  # 
  # Generate session key for encryption
  #
  sessKey = opts[:session_key]
  if sessKey.nil?
    # generate session key...
    cc = Gcrypto::CryptoContext.instance(:aes)
    cc.mode = "GCM"
    cc.random_iv
    cc.random_key
    sessKey = cc
  else
    # TODO: user provided session key?
    TcryptoJava::GConf.instance.glog.error "User given session key is not yet supported."  
    cc = Gcrypto::CryptoContext.instance(:aes)
    cc.mode = "GCM"
    cc.random_iv
    cc.random_key
    sessKey = cc
  end
  # 
  # session key generated!
  #
  
  #
  # Generate session key for data integrity check
  # Security wise, using different key for different purpose
  # has its objectives... So make new key then...
  #
  macKey = Gcrypto::CryptoContext.instance(:aes)
  macKey.mode = "GCM"
  macKey.random_iv
  macKey.random_key
  # 
  # MAC session key generated
  #

  # 
  # Create the encrypted key structure for recipients
  # 
  keys = []
  keys << Tcrypto::TagProvider.encode(:bin, Gcrypto::SecretKey.dump(sessKey.key))
  keys << Tcrypto::TagProvider.encode(:bin, Gcrypto::SecretKey.dump(macKey.key))
  keysSeq = Tcrypto::TagProvider.encode(:seq, keys)
  keysBin = Tcrypto::TagProvider.to_bin(keysSeq)
  # 

  # 
  # Construct recipient list 
  # 
  rcp = []
  rcpts.each do |re|
    if re.is_a?(Tcrypto::PasswordRecipient) #re.is_a?(String)
      # password
      if re.password.nil? or re.password.empty? or re.challenge.nil? or re.challenge.empty?
        raise Tcrypto::Error, "Challenge and Password must present"
      end
      
      pc = Gcrypto::CryptoContext.instance(:aes)
      pc.mode = "GCM"
      pc.derive_key(re.password)
      enc = Gcrypto::SecretKeyCrypto.encrypt({ bin: keysBin, crypto_context: pc })
      rcp << Tcrypto::CryptoHeader::PassRecipient.new(enc,pc,re)

    elsif Pkernel::Certificate.is_cert_object?(re)
      # X509 certificate
      TcryptoJava::GConf.instance.glog.error "Found certificate recipient"
      #enc = Gcrypto::KeyPairCrypto.encrypt({ bin: Gcrypto::SecretKey.dump(sessKey.key), recipient: re }) 
      enc = Gcrypto::KeyPairCrypto.encrypt({ bin: keysBin, recipient: re }) 
      rcp << Tcrypto::CryptoHeader::CertRecipient.new(enc,re)
      
    elsif Pkernel::KeyPair.is_public_key?(re)
      # public key
      TcryptoJava::GConf.instance.glog.error "Found public key recipient."
      enc = Gcrypto::KeyPairCrypto.encrypt({ bin: keysBin, recipient: re }) 
      rcp << Tcrypto::CryptoHeader::PublicKeyRecipient.new(enc,re)
      
    elsif re.is_a?(Gcrypto::CryptoContext) #Gcrypto::SecretKey.is_secret_key?(re)
      # crypto context... could be hardware key...
      enc = Gcrypto::SecretKeyCrypto.encrypt({ bin: keysBin, crypto_context: re })
      rcp << Tcrypto::CryptoHeader::SymKeyRecipient.new(enc, re)
      
    else
      raise TcryptoJava::Error, "Unknown recipient type #{re.class}"
    end
  end 
  # 
  # Recipient list done...
  #

  # check if data signing is defined...
  #sid = opts[:identity]
  #sign = nil
  #if not sid.nil?
  #  # data signing activated...
  #  sign = Gcrypto::KeyPairCrypto.sign_init({ identity: sid })
  #end
  
  # 
  # construct parameter to encrypt the payload...
  #
  encPara = { crypto_context: sessKey }
  if not opts[:file].nil?
    encPara[:file] = opts[:file]
    # if input is file, temporary content output also in file
    #encPara[:outFile] = java.io.File.new(java.lang.System.getProperty('java.io.tmpdir'),SecureRandom.uuid).absolute_path
    encPara[:outFile] = java.io.File.createTempFile('enc_',nil).absolute_path
    #encPara[:outFile].deleteOnExit
  elsif not opts[:bin].nil?
    encPara[:bin] = opts[:bin]
  else
    raise Tcrypto::Error, "No input given for tc encryption."
  end


  # 
  # Initiate keyed-HMAC operation for plain text
  #
  macCtx = Gcrypto::SecretKeyCrypto.sign_init({ crypto_context: macKey })
  
  # 
  # Encrypt output with session key
  #
  enc = Gcrypto::SecretKeyCrypto.encrypt(encPara) do |ops, para|
    
    if ops == :before_enc
      # Sign data along the way if it is defined
      #if not sign.nil? 
      #  Gcrypto::KeyPairCrypto.sign_update(sign, para)
      #end

      Gcrypto::SecretKeyCrypto.sign_update(macCtx, para)
    end

  end
  # 
  # data encryption with session key done
  #

  mac = Gcrypto::SecretKeyCrypto.sign_finalize(macCtx) 
  #signature = nil
  #if not sign.nil?
  #  signature = Gcrypto::KeyPairCrypto.sign_finalize(sign)
  #end
  # 
  # end encryption with session key
  #

  # recipient shall be in header
  encodePara = { recipients: rcp }
  # signature shall be in header
  #encodePara[:signature] = signature if not signature.nil?
  encodePara[:mac] = mac
  # payload is payload
  if encPara[:outFile].nil?
    encodePara[:payload] = { bin: enc }
  else
    encodePara[:payload] = { file: encPara[:outFile] }
  end
  
  # crypto context
  encodePara[:crypto_context] = cc
  encodePara[:mac_context] = macKey
  encodePara[:auto_remove_staging] = opts[:auto_remove_staging]
  encodePara[:temp_dir] = opts[:temp_dir]
  encodePara[:staging_path] = opts[:staging_path]

  # 
  # Setting final completed/combined output destination
  #
  outFile = opts[:outFile]
  if outFile.nil? or outFile.empty?
    out = java.io.ByteArrayOutputStream.new
  else
    Tcrypto::GConf.instance.glog.debug "Setting final combined output file #{outFile}"
    out = java.io.FileOutputStream.new(outFile)
  end

  # returns header and payload as seperate fields
  res = Tcrypto.encode(:crypto, encodePara, { out: out })
  
  if outFile.nil? or outFile.empty?
    res[:bin] = out.toByteArray
  else
    out.flush
    out.close
    
    res[:file] = outFile
  end

  res
end