require_relative '../data_conversion' module Ccrypto module Java class PBKDF2Engine include TR::CondUtils include DataConversion class PBKDF2EngineException < KDFEngineException; end class SupportedPBKDF2HMAC include Ccrypto::InMemoryRecord end def self.supported_pbkdf2_digests if @supportedHmac.nil? @supportedHmac = SupportedPBKDF2HMAC.load("cj_pbkdf2") logger.debug "supported hmac : #{@supportedHmac.empty?}" if @supportedHmac.empty? HMACEngine.supported_hmac_configs.each do |hm| begin algo = "PBKDF2With#{hm.provider_config[:hmac_algo]}" prov = hm.provider_config[:jce_provider] javax.crypto.SecretKeyFactory.getInstance(algo, prov) logger.debug "PBKDF2 algo #{algo} is good" hm.provider_config[:hmac_algo] = algo logger.debug "Registering PBKDF2 config #{hm.inspect}" @supportedHmac.register(hm, { tag_under: :algo, tag_value: hm.digest_config.algo }) #@supportedHmac[algo] = hm rescue Exception => ex logger.debug "HMAC algo #{algo} failed with PBKDF2 with error #{ex}" end end @supportedHmac.save("cj_pbkdf2") end end @supportedHmac end def self.find_supported_hmac_by_digest(algo) supported_pbkdf2_digests.find( algo: algo ) end private def self.logger Ccrypto::Java.logger(:pbkdf2_eng_c) end public def initialize(*args, &block) @config = args.first raise PBKDF2EngineException, "KDF config is expected. Given #{@config}" if not @config.is_a?(Ccrypto::PBKDF2Config) raise PBKDF2EngineException, "Output bit length (outBitLength) value is not given or not a positive value (#{@config.outBitLength})" if is_empty?(@config.outBitLength) or @config.outBitLength <= 0 if is_empty?(@config.digest) @config.digest = default_digest else case @config.digest when String, Symbol dig = self.class.find_supported_hmac_by_digest(@config.digest) raise PBKDF2EngineException, "Cannot find digest '#{@config.digest}'" if is_empty?(dig) logger.warn "More than 1 result for supported hmac by digest found. Found #{dig.length}" if dig.length > 1 @config.digest = dig.first when Ccrypto::HMACConfig else raise PBKDF2EngineException, "HMACConfig is expected instead got '#{@config.digest.class}'" end raise PBKDF2EngineException, "HMACConfig is required to be provider initialized HMACConfig. Please get the HMACConfig via the supported_hmac from PBKDF2" if is_empty?(@config.digest.provider_config[:hmac_algo]) end #@config.salt = SecureRandom.random_bytes(16) if is_empty?(@config.salt) end def derive(input, output = :binary) cinput = java.lang.String.new(to_java_bytes(input)) #raise KDFEngineException, "Given input is not a String" if not input.is_a?(String) begin algo = @config.digest.provider_config[:hmac_algo] prov = @config.digest.provider_config[:jce_provider] if not_empty?(prov) skf = javax.crypto.SecretKeyFactory.getInstance(algo, prov) else skf = javax.crypto.SecretKeyFactory.getInstance(algo) end # Java API 1st parameter is char[] keySpec = javax.crypto.spec.PBEKeySpec.new(cinput.to_java.toCharArray, to_java_bytes(@config.salt), @config.iter, @config.outBitLength) sk = skf.generateSecret(keySpec) out = sk.encoded case output when :b64 to_b64(out) when :hex to_hex(out) else out end rescue Exception => ex raise KDFEngineException, ex end end def default_digest self.class.find_supported_hmac_by_digest("sha256").first end private def logger Ccrypto::Java.logger(:pbkdf2_eng) end end end end