aesctr-ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +23 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/aesctr-ruby.gemspec +60 -0
- data/lib/aesctr-ruby.rb +283 -0
- data/test/helper.rb +18 -0
- data/test/test_aesctr-ruby.rb +11 -0
- metadata +126 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler", "~> 1.0.0"
|
12
|
+
gem "jeweler", "~> 1.8.3"
|
13
|
+
# gem "rcov", ">= 0"
|
14
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.8.3)
|
6
|
+
bundler (~> 1.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
rdoc
|
10
|
+
json (1.6.5)
|
11
|
+
rake (0.9.2.2)
|
12
|
+
rdoc (3.12)
|
13
|
+
json (~> 1.4)
|
14
|
+
shoulda (2.11.3)
|
15
|
+
|
16
|
+
PLATFORMS
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
bundler (~> 1.0.0)
|
21
|
+
jeweler (~> 1.8.3)
|
22
|
+
rdoc (~> 3.12)
|
23
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Diego Suarez
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
= aesctr-ruby
|
2
|
+
|
3
|
+
Aunque puede que se deba a lo torpe que soy, tras mucho buscar no he encontrado ninguna implementacion del cifrado AES-CTR (modo contador) en Ruby. Hay dos implementaciones de referencia en Javascript y PHP escritas por Chris Veness (http://www.movable-type.co.uk/scripts/aes.html), y hay al menos otro port que yo conozca a ActionScript, pero faltaba un port en Ruby.
|
4
|
+
|
5
|
+
Personalmente, decidi hacer el port en vez de usar otra de las implementaciones de AES por la sencilla razon de que necesitaba interoperabilidad con servicios web que utilizaban la implementacion de PHP citada.
|
6
|
+
|
7
|
+
==Uso==
|
8
|
+
Simple:
|
9
|
+
|
10
|
+
require 'aesctr-ruby'
|
11
|
+
plain= "Hola mundo del cifrado!"
|
12
|
+
pass = "contrasena"
|
13
|
+
puts "Cadena original:"+plain
|
14
|
+
puts "Contrasena:"+pass
|
15
|
+
puts "Cadena cifrada y descifrada: "+AesCtr.decrypt(AesCtr.encrypt(plain, pass, 256), pass, 256)
|
16
|
+
|
17
|
+
Dos funciones principales, AesCtr.crypt y AesCtr.decrypt. Los parametros son el texto, la contraseña de cifrado, y el numero de bits a usar (128|192|256). "encrypt" devuelve cifrado el texto (y codificado en base64 para evitar problemas) y decrypt toma el texto cifrado (en base64 tb) y devuelve el original.
|
18
|
+
|
19
|
+
== Copyright
|
20
|
+
|
21
|
+
Copyright (c) 2012 Diego suarez. See LICENSE.txt for
|
22
|
+
further details.
|
23
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "aesctr-ruby"
|
18
|
+
gem.homepage = "http://github.com/diegosuarez/aesctr-ruby"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{AES in counter mode for ruby}
|
21
|
+
gem.description = "La descripcion del resumen ya dice lo que hay :)"
|
22
|
+
gem.email = "diego.suargarcia@gmail.com"
|
23
|
+
gem.authors = ["Diego Suarez"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
#require 'rcov'
|
36
|
+
#Rcov::RcovTask.new do |test|
|
37
|
+
# test.libs << 'test'
|
38
|
+
# test.pattern = 'test/**/test_*.rb'
|
39
|
+
# test.verbose = true
|
40
|
+
# test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
#end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rdoc/task'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "aesctr-ruby #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/aesctr-ruby.gemspec
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{aesctr-ruby}
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Diego Suarez"]
|
12
|
+
s.date = %q{2012-02-18}
|
13
|
+
s.description = %q{La descripcion del resumen ya dice lo que hay :)}
|
14
|
+
s.email = %q{diego.suargarcia@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"aesctr-ruby.gemspec",
|
28
|
+
"lib/aesctr-ruby.rb",
|
29
|
+
"test/helper.rb",
|
30
|
+
"test/test_aesctr-ruby.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/diegosuarez/aesctr-ruby}
|
33
|
+
s.licenses = ["MIT"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.7}
|
36
|
+
s.summary = %q{AES in counter mode for ruby}
|
37
|
+
|
38
|
+
if s.respond_to? :specification_version then
|
39
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
40
|
+
s.specification_version = 3
|
41
|
+
|
42
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
43
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
44
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
45
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
46
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
47
|
+
else
|
48
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
49
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
50
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
51
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
55
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
56
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/lib/aesctr-ruby.rb
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Aes
|
4
|
+
# sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [§5.1.1]
|
5
|
+
def self.sBox
|
6
|
+
[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
|
7
|
+
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
|
8
|
+
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
|
9
|
+
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
|
10
|
+
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
|
11
|
+
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
|
12
|
+
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
|
13
|
+
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
|
14
|
+
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
|
15
|
+
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
|
16
|
+
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
|
17
|
+
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
|
18
|
+
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
|
19
|
+
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
|
20
|
+
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
|
21
|
+
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]
|
22
|
+
end
|
23
|
+
|
24
|
+
# rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
|
25
|
+
def self.rCon
|
26
|
+
[ [0x00, 0x00, 0x00, 0x00],
|
27
|
+
[0x01, 0x00, 0x00, 0x00],
|
28
|
+
[0x02, 0x00, 0x00, 0x00],
|
29
|
+
[0x04, 0x00, 0x00, 0x00],
|
30
|
+
[0x08, 0x00, 0x00, 0x00],
|
31
|
+
[0x10, 0x00, 0x00, 0x00],
|
32
|
+
[0x20, 0x00, 0x00, 0x00],
|
33
|
+
[0x40, 0x00, 0x00, 0x00],
|
34
|
+
[0x80, 0x00, 0x00, 0x00],
|
35
|
+
[0x1b, 0x00, 0x00, 0x00],
|
36
|
+
[0x36, 0x00, 0x00, 0x00] ]
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# AES Cipher function: encrypt 'input' state with Rijndael algorithm
|
41
|
+
# applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
|
42
|
+
# @param int[] input 16-byte (128-bit) input state array
|
43
|
+
# @param int[][] w Key schedule as 2D byte-array (Nr+1 x Nb bytes)
|
44
|
+
# @returns int[] Encrypted output state array
|
45
|
+
|
46
|
+
def self.cipher(input, w) #main Cipher function [§5.1]
|
47
|
+
nb = 4; #block size (in words): no of columns in state (fixed at 4 for AES)
|
48
|
+
nr = w.length/nb -1 ## no of rounds: 10/12/14 for 128/192/256-bit keys
|
49
|
+
state = [[],[],[],[]] # initialise 4xNb byte-array 'state' with input [§3.4]
|
50
|
+
|
51
|
+
(0...(4*nb)).each do |i|
|
52
|
+
state[i%4][(i/4.0).floor] = input[i]
|
53
|
+
end
|
54
|
+
state = self.addRoundKey(state,w,0,nb)
|
55
|
+
(1...nr).each do |round|
|
56
|
+
state = Aes.subBytes(state, nb)
|
57
|
+
state = Aes.shiftRows(state, nb)
|
58
|
+
state = Aes.mixColumns(state, nb)
|
59
|
+
state = Aes.addRoundKey(state, w,round,nb)
|
60
|
+
end
|
61
|
+
state = Aes.subBytes(state, nb)
|
62
|
+
state = Aes.shiftRows(state, nb)
|
63
|
+
state = Aes.addRoundKey(state, w, nr,nb)
|
64
|
+
|
65
|
+
output = []
|
66
|
+
(0...(4*nb)).each{|i| output[i] = state[i%4][(i/4.0).floor]}
|
67
|
+
output
|
68
|
+
end
|
69
|
+
|
70
|
+
# Perform Key Expansion to generate a Key Schedule
|
71
|
+
#
|
72
|
+
# @param int[] key Key as 16/24/32-byte array
|
73
|
+
# @returns int[][] Expanded key schedule as 2D byte-array (Nr+1 x Nb bytes)
|
74
|
+
def self.keyExpansion(key)
|
75
|
+
nb = 4 # block size (in words): no of columns in state (fixed at 4 for AES)
|
76
|
+
nk = key.length/4 # key length (in words): 4/6/8 for 128/192/256-bit keys
|
77
|
+
nr = nk + 6 # no of rounds: 10/12/14 for 128/192/256-bit keys
|
78
|
+
|
79
|
+
w = []
|
80
|
+
temp = []
|
81
|
+
0.upto(nk-1){|i| w[i] = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]] }
|
82
|
+
(nk...(nb*(nr+1))).each do |i|
|
83
|
+
w[i] = []
|
84
|
+
0.upto(3){|t| temp[t] = w[i-1][t]}
|
85
|
+
if(i % nk == 0)
|
86
|
+
temp = self.subWord(self.rotWord(temp))
|
87
|
+
0.upto(3){|t| temp[t] ^= self.rCon[i/nk][t]}
|
88
|
+
elsif(nk > 6 && i%nk==4)
|
89
|
+
temp = self.subWord(temp)
|
90
|
+
end
|
91
|
+
0.upto(3){|t| w[i][t] = w[i-nk][t] ^ temp[t] }
|
92
|
+
end
|
93
|
+
w
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
# apply SBox to state S [§5.1.1]
|
98
|
+
def self.subBytes(s,nb)
|
99
|
+
0.upto(3) do |r|
|
100
|
+
0.upto(nb-1){|c| s[r][c] = self.sBox[s[r][c]]}
|
101
|
+
end
|
102
|
+
s
|
103
|
+
end
|
104
|
+
# shift row r of state S left by r bytes [§5.1.2]
|
105
|
+
def self.shiftRows(s,nb)
|
106
|
+
t = []
|
107
|
+
1.upto(3) do |r|
|
108
|
+
0.upto(3){|c| t[c] = s[r][(c+r)%nb]} # shift into temp copy
|
109
|
+
0.upto(3){|c| s[r][c] = t[c]} # and copy back
|
110
|
+
end # note that this will work for nb =4,5,6, but not 7,8 (always 4 for AES):
|
111
|
+
s # see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
|
112
|
+
end
|
113
|
+
|
114
|
+
# combine bytes of each col of state S [§5.1.3]
|
115
|
+
def self.mixColumns(s, nb)
|
116
|
+
0.upto(3) do |c|
|
117
|
+
a=[] # 'a' is a copy of the current column from 's'
|
118
|
+
b=[] # 'b' is a•{02} in GF(2^8)
|
119
|
+
0.upto(3) do |i|
|
120
|
+
a[i] = s[i][c]
|
121
|
+
b[i] = (s[i][c]&0x80==0) ? (s[i][c]<<1) ^ 0x011b : s[i][c]<<1
|
122
|
+
end
|
123
|
+
# a[n] ^ b[n] is a•{03} in GF(2^8)
|
124
|
+
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3] # 2*a0 + 3*a1 + a2 + a3
|
125
|
+
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3] # a0 * 2*a1 + 3*a2 + a3
|
126
|
+
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3] # a0 + a1 + 2*a2 + 3*a3
|
127
|
+
s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3] # 3*a0 + a1 + a2 + 2*a3
|
128
|
+
end
|
129
|
+
s
|
130
|
+
end
|
131
|
+
|
132
|
+
# xor Round Key into state S [§5.1.4]
|
133
|
+
def self.addRoundKey(state, w, rnd, nb)
|
134
|
+
0.upto(3) do |r|
|
135
|
+
0.upto(nb-1){|c| state[r][c] ^= w[rnd*4+c][r]}
|
136
|
+
end
|
137
|
+
state
|
138
|
+
end
|
139
|
+
|
140
|
+
# apply SBox to 4-byte word w
|
141
|
+
def self.subWord(w)
|
142
|
+
0.upto(3){|i| w[i] = self.sBox[w[i]]}
|
143
|
+
w
|
144
|
+
end
|
145
|
+
|
146
|
+
# rotate 4-byte word w left by one byte
|
147
|
+
def self.rotWord(w)
|
148
|
+
tmp = w[0]
|
149
|
+
0.upto(2){|i| w[i] = w[i+1]}
|
150
|
+
w[3] = tmp;
|
151
|
+
w
|
152
|
+
end
|
153
|
+
|
154
|
+
end #AES
|
155
|
+
|
156
|
+
module AesCtr
|
157
|
+
#
|
158
|
+
# Encrypt a text using AES encryption in Counter mode of operation
|
159
|
+
#
|
160
|
+
# Unicode multi-byte character safe
|
161
|
+
#
|
162
|
+
# @param string plaintext Source text to be encrypted
|
163
|
+
# @param string password The password to use to generate a key
|
164
|
+
# @param int nBits Number of bits to be used in the key (128, 192, or 256)
|
165
|
+
# @returns string Encrypted text
|
166
|
+
def self.encrypt(plaintext, password, nBits)
|
167
|
+
blockSize = 16 # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
|
168
|
+
return '' unless(nBits==128 || nBits==192 || nBits==256)
|
169
|
+
|
170
|
+
# use AES itself to encrypt password to get cipher key (using plain password as source for key
|
171
|
+
# expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)
|
172
|
+
nBytes = nBits/8 # no bytes in key (16/24/32)
|
173
|
+
pwBytes = []
|
174
|
+
0.upto(nBytes-1){|i| pwBytes[i] = password.bytes.to_a[i] & 0xff || 0} # use 1st 16/24/32 chars of password for key #warn
|
175
|
+
key = Aes::cipher(pwBytes, Aes::keyExpansion(pwBytes)) # gives us 16-byte key
|
176
|
+
key = key + key[0, nBytes-16] # expand key to 16/24/32 bytes long
|
177
|
+
# initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec,
|
178
|
+
# [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
|
179
|
+
counterBlock = []
|
180
|
+
nonce = Time.now.to_i #42200
|
181
|
+
nonceMs = nonce%1000
|
182
|
+
nonceSec = (nonce/1000.0).floor
|
183
|
+
nonceRnd = (rand() *0xffff).floor #13
|
184
|
+
0.upto(1){|i| counterBlock[i] = self.urs(nonceMs, i*8) &0xff }
|
185
|
+
0.upto(1){|i| counterBlock[i+2] = self.urs(nonceRnd, i*8) &0xff }
|
186
|
+
0.upto(3){|i| counterBlock[i+4] = self.urs(nonceSec,i*8) & 0xff}
|
187
|
+
|
188
|
+
# and convert it to a string to go on the front of the ciphertext
|
189
|
+
ctrTxt = ''
|
190
|
+
0.upto(7){|i| ctrTxt += counterBlock[i].chr}
|
191
|
+
# generate key schedule - an expansion of the key into distinct Key Rounds for each round
|
192
|
+
keySchedule = Aes.keyExpansion(key)
|
193
|
+
#p "rks:",keySchedule
|
194
|
+
blockCount = (plaintext.length/blockSize.to_f).ceil
|
195
|
+
|
196
|
+
ciphertxt = []
|
197
|
+
0.upto(blockCount-1) do |b|
|
198
|
+
# set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
|
199
|
+
# done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
|
200
|
+
0.upto(3){|c| counterBlock[15-c] = self.urs(b,c*8) & 0xff}
|
201
|
+
0.upto(3){|c| counterBlock[15-c-4] = self.urs(b/0x100000000,c*8)}
|
202
|
+
|
203
|
+
cipherCntr = Aes.cipher(counterBlock, keySchedule) # -- encrypt counter block --
|
204
|
+
# block size is reduced on final block
|
205
|
+
blockLength = b < blockCount-1 ? blockSize : (plaintext.length-1) % blockSize + 1
|
206
|
+
cipherChar = [];
|
207
|
+
0.upto(blockLength-1) do |i|
|
208
|
+
cipherChar[i] = (cipherCntr[i] ^ plaintext.bytes.to_a[b*blockSize+i]).chr
|
209
|
+
end
|
210
|
+
ciphertxt[b] = cipherChar.join('')
|
211
|
+
end
|
212
|
+
|
213
|
+
ciphertext = ctrTxt + ciphertxt.join('')
|
214
|
+
ciphertext = Base64.encode64(ciphertext).gsub(/\n/,'')+"\n"; # encode in base64
|
215
|
+
end
|
216
|
+
|
217
|
+
# Decrypt a text encrypted by AES in counter mode of operation
|
218
|
+
#
|
219
|
+
# @param string ciphertext Source text to be encrypted
|
220
|
+
# @param string password The password to use to generate a key
|
221
|
+
# @param int nBits Number of bits to be used in the key (128, 192, or 256)
|
222
|
+
# @returns string
|
223
|
+
# Decrypted text
|
224
|
+
def self.decrypt(ciphertext, password, nBits)
|
225
|
+
blockSize = 16 # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
|
226
|
+
return '' unless(nBits==128 || nBits==192 || nBits==256)
|
227
|
+
ciphertext = Base64.decode64(ciphertext);
|
228
|
+
|
229
|
+
nBytes = nBits/8 # no bytes in key (16/24/32)
|
230
|
+
pwBytes = []
|
231
|
+
0.upto(nBytes-1){|i| pwBytes[i] = password.bytes.to_a[i] & 0xff || 0}
|
232
|
+
key = Aes::cipher(pwBytes, Aes::keyExpansion(pwBytes)) # gives us 16-byte key
|
233
|
+
key = key.concat(key.slice(0, nBytes-16)) # expand key to 16/24/32 bytes long
|
234
|
+
# recover nonce from 1st 8 bytes of ciphertext
|
235
|
+
counterBlock = []
|
236
|
+
ctrTxt = ciphertext[0,8]
|
237
|
+
0.upto(7){|i| counterBlock[i] = ctrTxt.bytes.to_a[i]}
|
238
|
+
|
239
|
+
#generate key Schedule
|
240
|
+
keySchedule = Aes.keyExpansion(key);
|
241
|
+
|
242
|
+
# separate ciphertext into blocks (skipping past initial 8 bytes)
|
243
|
+
nBlocks = ((ciphertext.length-8)/blockSize.to_f).ceil
|
244
|
+
ct=[]
|
245
|
+
0.upto(nBlocks-1){|b|ct[b] = ciphertext[8+b*blockSize, 16]}
|
246
|
+
|
247
|
+
ciphertext = ct; # ciphertext is now array of block-length strings
|
248
|
+
|
249
|
+
# plaintext will get generated block-by-block into array of block-length strings
|
250
|
+
plaintxt = [];
|
251
|
+
0.upto(nBlocks-1) do |b|
|
252
|
+
0.upto(3){|c| counterBlock[15-c] = self.urs(b,c*8) & 0xff}
|
253
|
+
0.upto(3){|c| counterBlock[15-c-4] = self.urs((b+1)/(0x100000000-1),c*8) & 0xff}
|
254
|
+
cipherCntr = Aes.cipher(counterBlock, keySchedule) # encrypt counter block
|
255
|
+
plaintxtByte = []
|
256
|
+
0.upto(ciphertext[b].length - 1) do |i|
|
257
|
+
# -- xor plaintxt with ciphered counter byte-by-byte --
|
258
|
+
plaintxtByte[i] = (cipherCntr[i] ^ ciphertext[b].bytes.to_a[i]).chr;
|
259
|
+
end
|
260
|
+
plaintxt[b] = plaintxtByte.join('')
|
261
|
+
end
|
262
|
+
plaintext = plaintxt.join('')
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
#
|
267
|
+
# Unsigned right shift function, since Ruby has neither >>> operator nor unsigned ints
|
268
|
+
#
|
269
|
+
# @param a number to be shifted (32-bit integer)
|
270
|
+
# @param b number of bits to shift a to the right (0..31)
|
271
|
+
# @return a right-shifted and zero-filled by b bits
|
272
|
+
def self.urs(a, b)
|
273
|
+
a &= 0xffffffff
|
274
|
+
b &= 0x1f
|
275
|
+
if (a&0x80000000 && b>0) # if left-most bit set
|
276
|
+
a = ((a>>1) & 0x7fffffff) # right-shift one bit & clear left-most bit
|
277
|
+
a = a >> (b-1) # remaining right-shifts
|
278
|
+
else # otherwise
|
279
|
+
a = (a>>b); # use normal right-shift
|
280
|
+
end
|
281
|
+
a
|
282
|
+
end
|
283
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'aesctr-ruby'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestAesctrRuby < Test::Unit::TestCase
|
4
|
+
should "be equal before and after crypt" do
|
5
|
+
plain= "Hola mundo del cifrado!"
|
6
|
+
pass = "contrasena"
|
7
|
+
puts "Cadena original:"+plain
|
8
|
+
puts "Contrasena:"+pass
|
9
|
+
assert_equal plain, AesCtr.decrypt(AesCtr.encrypt(plain, pass, 192), pass, 192)
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aesctr-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
prerelease: false
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Diego Suarez
|
13
|
+
autorequire: !!null
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
date: 2012-02-18 00:00:00.000000000 +01:00
|
17
|
+
default_executable: !!null
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: shoulda
|
21
|
+
requirement: &15647480 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
type: :development
|
30
|
+
prerelease: false
|
31
|
+
version_requirements: *15647480
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rdoc
|
34
|
+
requirement: &15646620 !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.12'
|
40
|
+
segments:
|
41
|
+
- 3
|
42
|
+
- 12
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: *15646620
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: &15643680 !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.0.0
|
54
|
+
segments:
|
55
|
+
- 1
|
56
|
+
- 0
|
57
|
+
- 0
|
58
|
+
type: :development
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: *15643680
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: jeweler
|
63
|
+
requirement: &15642860 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.8.3
|
69
|
+
segments:
|
70
|
+
- 1
|
71
|
+
- 8
|
72
|
+
- 3
|
73
|
+
type: :development
|
74
|
+
prerelease: false
|
75
|
+
version_requirements: *15642860
|
76
|
+
description: La descripcion del resumen ya dice lo que hay :)
|
77
|
+
email: diego.suargarcia@gmail.com
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files:
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.rdoc
|
83
|
+
files:
|
84
|
+
- .document
|
85
|
+
- Gemfile
|
86
|
+
- Gemfile.lock
|
87
|
+
- LICENSE.txt
|
88
|
+
- README.rdoc
|
89
|
+
- Rakefile
|
90
|
+
- VERSION
|
91
|
+
- aesctr-ruby.gemspec
|
92
|
+
- lib/aesctr-ruby.rb
|
93
|
+
- test/helper.rb
|
94
|
+
- test/test_aesctr-ruby.rb
|
95
|
+
has_rdoc: true
|
96
|
+
homepage: http://github.com/diegosuarez/aesctr-ruby
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
post_install_message: !!null
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
hash: -2105260795538654983
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project: !!null
|
122
|
+
rubygems_version: 1.3.7
|
123
|
+
signing_key: !!null
|
124
|
+
specification_version: 3
|
125
|
+
summary: AES in counter mode for ruby
|
126
|
+
test_files: []
|