aes_key_wrap-1.1.0/0000755000004100000410000000000013704150513014211 5ustar www-datawww-dataaes_key_wrap-1.1.0/.travis.yml0000644000004100000410000000117213704150513016323 0ustar www-datawww-datascript: bundle exec ruby test/aes_key_wrap.rb language: ruby # test on old rubies rvm: - 2.3.8 - 2.4.10 - 2.5.8 - 2.6.6 # run latest ruby differently (run codeclimate) matrix: include: - rvm: 2.7.1 # code climate config env: CC_TEST_REPORTER_ID=112d90dacd3bbf36bbf39282309a52581175ae544be2cf8ef3b8c0c0221515f5 before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT aes_key_wrap-1.1.0/README.md0000644000004100000410000000221013704150513015463 0ustar www-datawww-data[![Build Status](https://travis-ci.org/tomdalling/aes_key_wrap.svg?branch=master)](https://travis-ci.org/tomdalling/aes_key_wrap) [![Test Coverage](https://codeclimate.com/github/tomdalling/aes_key_wrap/badges/coverage.svg)](https://codeclimate.com/github/tomdalling/aes_key_wrap) # AESKeyWrap A Ruby implementation of AES Key Wrap, a.k.a RFC 3394, a.k.a NIST Key Wrap. ## Usage To wrap a key, call `AESKeyWrap.wrap` with: - The plain text key - A key-encrypting key (KEK) - An "initial value" (optional) ```ruby require 'aes_key_wrap' plaintext_key = ['00112233445566778899AABBCCDDEEFF'].pack('H*') #binary string kek = ['000102030405060708090A0B0C0D0E0F'].pack('H*') # binary string iv = ['DEADBEEFC0FFEEEE'].pack("H*") # binary string (always 8 bytes) wrapped_key = AESKeyWrap.wrap(plaintext_key, kek, iv) # iv is optional ``` To unwrap a key, call `AESKeyWrap.unwrap`: ```ruby unwrapped = AESKeyWrap.unwrap(wrapped_key, kek, iv) # iv is optional ``` There also `unwrap!`, which throws an exception if unwrapping fails, instead of returning nil. ## Contributing Make sure it's got tests, then do the usual fork and pull request hooha. aes_key_wrap-1.1.0/bin/0000755000004100000410000000000013704150513014761 5ustar www-datawww-dataaes_key_wrap-1.1.0/bin/console0000755000004100000410000000052113704150513016347 0ustar www-datawww-data#!/usr/bin/env ruby require "bundler/setup" require "aes_key_wrap" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start aes_key_wrap-1.1.0/bin/setup0000755000004100000410000000016313704150513016047 0ustar www-datawww-data#!/bin/bash set -euo pipefail IFS=$'\n\t' bundle install # Do any other automated setup that you need to do here aes_key_wrap-1.1.0/CHANGELOG.md0000644000004100000410000000141513704150513016023 0ustar www-datawww-data# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.1.0] - 2020-07-12 ### Added - IV arguments can be either `String`s or `Integer`s. Previously, they could only be `Integer`s. This is a backwards-compatible addition as long as you aren't doing something freaky with IVs, like using negative numbers (they are supposed to be unsigned). ## [1.0.1] - 2015-04-24 ### Fixed - Didn't work unless you had `require 'openssl'` somewhere first. The gem now `require`s its own dependencies, surprising no one. ## [1.0.0] - 2015-04-24 ### Added - Everything (Initial release) aes_key_wrap-1.1.0/.gitignore0000644000004100000410000000012713704150513016201 0ustar www-datawww-data/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ aes_key_wrap-1.1.0/lib/0000755000004100000410000000000013704150513014757 5ustar www-datawww-dataaes_key_wrap-1.1.0/lib/aes_key_wrap.rb0000644000004100000410000001074413704150513017763 0ustar www-datawww-datarequire 'openssl' ## # A Ruby implementation of AES Key Wrap, a.k.a RFC 3394, a.k.a NIST Key Wrapping # module AESKeyWrap DEFAULT_IV = 0xA6A6A6A6A6A6A6A6 IV_SIZE = 8 # bytes UnwrapFailedError = Class.new(StandardError) class << self ## # Wraps a key using a key-encrypting key (KEK) # # This is an implementation of the "index based" algorithm # specified in section 2.2.1 of RFC 3394: # http://www.ietf.org/rfc/rfc3394.txt # # @param unwrapped_key [String] The plaintext key to be wrapped, as a binary string # @param kek [String] The key-encrypting key, as a binary_string # @param iv [Integer, String] The "initial value", as either an unsigned # 64-bit integer (e.g. `0xDEADBEEFC0FFEEEE`) or an 8-byte string (e.g. # `"\xDE\xAD\xBE\xEF\xC0\xFF\xEE\xEE"`). # @return [String] The wrapped key, as a binary string # def wrap(unwrapped_key, kek, iv=DEFAULT_IV) # 1) Initialize variables. # # P: buffer (from unwrapped_key) # A: buffer[0] # R: buffer # K: kek # n: block_count # AES: aes(:encrypt, _, _) # IV: iv buffer = [coerce_uint64(iv)] + unwrapped_key.unpack('Q>*') block_count = buffer.size - 1 # 2) Calculate intermediate values. # t: round 0.upto(5) do |j| 1.upto(block_count) do |i| round = block_count*j + i # In data = [buffer[0], buffer[i]].pack('Q>2') buffer[0], buffer[i] = aes(:encrypt, kek, data).unpack('Q>2') # Enc buffer[0] = buffer[0] ^ round # XorT end end # 3) Output the results. buffer.pack('Q>*') end ## # Unwraps an encrypted key using a key-encrypting key (KEK) # # This is an implementation of the "index based" algorithm # specified in section 2.2.2 of RFC 3394: # http://www.ietf.org/rfc/rfc3394.txt # # @param wrapped_key [String] The wrapped key (cyphertext), as a binary string # @param kek [String] The key-encrypting key, as a binary string # @param expected_iv [Integer, String] The IV used to wrap the key, as either # an unsigned 64-bit integer (e.g. `0xDEADBEEFC0FFEEEE`) or an 8-byte # string (e.g. `"\xDE\xAD\xBE\xEF\xC0\xFF\xEE\xEE"`). # @return [String] The unwrapped (plaintext) key as a binary string, or # `nil` if unwrapping failed due to `expected_iv` not matching the # decrypted IV # # @see #unwrap! # def unwrap(wrapped_key, kek, expected_iv=DEFAULT_IV) # 1) Initialize variables. # # C: buffer (from wrapped_key) # A: buffer[0] # R: buffer # n: block_count # K: kek # AES-1: aes(:decrypt, _, _) buffer = wrapped_key.unpack('Q>*') block_count = buffer.size - 1 # 2) Calculate intermediate values. # t: round 5.downto(0) do |j| block_count.downto(1) do |i| round = block_count*j + i # In buffer[0] = buffer[0] ^ round # XorT data = [buffer[0], buffer[i]].pack('Q>2') buffer[0], buffer[i] = aes(:decrypt, kek, data).unpack('Q>2') # Dec end end # 3) Output the results. if buffer[0] == coerce_uint64(expected_iv) buffer.drop(1).pack('Q>*') else nil end end ## # Exception-throwing version of #unwrap # # @see #unwrap # def unwrap!(*args) unwrap(*args) || raise(UnwrapFailedError, 'Unwrapped IV does not match') end private MAX_UINT64 = 0xFFFFFFFFFFFFFFFF def aes(encrypt_or_decrypt, key, data) decipher = OpenSSL::Cipher::AES.new(key.bytesize * 8, :ECB) decipher.send(encrypt_or_decrypt) decipher.key = key decipher.padding = 0 decipher.update(data) + decipher.final end def coerce_uint64(value) case value when Integer if value > MAX_UINT64 raise ArgumentError, "IV is too large to fit in a 64-bit unsigned integer" elsif value < 0 raise ArgumentError, "IV is not an unsigned integer (it's negative)" else value end when String if value.bytesize == IV_SIZE value.unpack("Q>").first else raise ArgumentError, "IV is not #{IV_SIZE} bytes long" end else raise ArgumentError, "IV is not valid: #{value.inspect}" end end end end aes_key_wrap-1.1.0/lib/aes_key_wrap/0000755000004100000410000000000013704150513017430 5ustar www-datawww-dataaes_key_wrap-1.1.0/lib/aes_key_wrap/version.rb0000644000004100000410000000005213704150513021437 0ustar www-datawww-datamodule AESKeyWrap VERSION = '1.1.0' end aes_key_wrap-1.1.0/Gemfile0000644000004100000410000000014113704150513015500 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in aes_key_wrap.gemspec gemspec aes_key_wrap-1.1.0/.ruby-version0000644000004100000410000000000413704150513016650 0ustar www-datawww-data2.7 aes_key_wrap-1.1.0/aes_key_wrap.gemspec0000644000004100000410000000170713704150513020234 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'aes_key_wrap/version' Gem::Specification.new do |spec| spec.name = 'aes_key_wrap' spec.version = AESKeyWrap::VERSION spec.authors = ['Tom Dalling'] spec.email = ['tom' + '@tom' + 'dalling.com'] spec.summary = %q{A Ruby implementation of AES Key Wrap, a.k.a RFC 3394, a.k.a NIST Key Wrap.} spec.homepage = 'https://github.com/tomdalling/aes_key_wrap' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.require_paths = ['lib'] spec.add_development_dependency 'test_bench', '~> 1.0' spec.add_development_dependency 'gem-release' # code climate doesn't support v0.18+ # see: https://github.com/codeclimate/test-reporter/issues/413 spec.add_development_dependency 'simplecov', '< 0.18' end aes_key_wrap-1.1.0/LICENSE.txt0000644000004100000410000000206613704150513016040 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2015 Tom Dalling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.