-
Notifications
You must be signed in to change notification settings - Fork 199
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,16 +25,17 @@ | |
# those of the authors and should not be interpreted as representing official | ||
# policies, either expressed or implied, of Salvatore Sanfilippo. | ||
|
||
require 'app_config' | ||
require 'rubygems' | ||
require 'hiredis' | ||
require 'redis' | ||
require 'page' | ||
require 'app_config' | ||
require 'sinatra' | ||
require 'json' | ||
require 'digest/sha1' | ||
require 'digest/md5' | ||
require 'comments' | ||
require 'pbkdf2' | ||
|
||
before do | ||
$r = Redis.new(:host => RedisHost, :port => RedisPort) if !$r | ||
|
@@ -632,10 +633,16 @@ def update_auth_token(user_id) | |
return new_auth_token | ||
end | ||
|
||
# Turn the password into an hashed one, using | ||
# SHA1(salt|password). | ||
# Turn the password into an hashed one, using PBKDF2 with HMAC-SHA1 | ||
# and 160 bit output. | ||
def hash_password(password) | ||
Digest::SHA1.hexdigest(PasswordSalt+password) | ||
p = PBKDF2.new do |p| | ||
p.iterations = PBKDF2Iterations | ||
p.password = password | ||
p.salt = PasswordSalt | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
antirez
Author
Owner
|
||
p.key_length = 160/8 | ||
end | ||
p.hex_string | ||
end | ||
|
||
# Return the user from the ID. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
# Modified by Salvatore Sanfilippo to only support HMAC-SHA1 from | ||
# ruby-hmac gem in order to drop the openssl dependency. | ||
# | ||
# Copyright (c) 2008 Sam Quigley <quigley@emerose.com> | ||
# Copyright (c) 2011 Salvatore Sanfilippo <antirez@gmail.com> | ||
# | ||
# 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. | ||
# | ||
# This license is sometimes referred to as "The MIT License" | ||
|
||
require 'rubygems' | ||
require 'hmac-sha1' | ||
|
||
class PBKDF2 | ||
def initialize(opts={}) | ||
# override with options | ||
opts.each_key do |k| | ||
if self.respond_to?("#{k}=") | ||
self.send("#{k}=", opts[k]) | ||
else | ||
raise ArgumentError, "Argument '#{k}' is not allowed" | ||
end | ||
end | ||
|
||
yield self if block_given? | ||
|
||
# set this to the default if nothing was given | ||
@key_length ||= 20 | ||
|
||
# make sure the relevant things got set | ||
raise ArgumentError, "password not set" if @password.nil? | ||
raise ArgumentError, "salt not set" if @salt.nil? | ||
raise ArgumentError, "iterations not set" if @iterations.nil? | ||
end | ||
attr_reader :key_length, :iterations, :salt, :password | ||
|
||
def key_length=(l) | ||
raise ArgumentError, "key too short" if l < 1 | ||
raise ArgumentError, "key too long" if l > ((2**32 - 1) * 20) | ||
@value = nil | ||
@key_length = l | ||
end | ||
|
||
def iterations=(i) | ||
raise ArgumentError, "iterations can't be less than 1" if i < 1 | ||
@value = nil | ||
@iterations = i | ||
end | ||
|
||
def salt=(s) | ||
@value = nil | ||
@salt = s | ||
end | ||
|
||
def password=(p) | ||
@value = nil | ||
@password = p | ||
end | ||
|
||
def value | ||
calculate! if @value.nil? | ||
@value | ||
end | ||
|
||
alias bin_string value | ||
|
||
def hex_string | ||
bin_string.unpack("H*").first | ||
end | ||
|
||
# return number of milliseconds it takes to complete one iteration | ||
def benchmark(iters = 400000) | ||
iter_orig = @iterations | ||
@iterations=iters | ||
start = Time.now | ||
calculate! | ||
time = Time.now - start | ||
@iterations = iter_orig | ||
return (time/iters) | ||
end | ||
|
||
protected | ||
|
||
# the pseudo-random function defined in the spec | ||
def prf(data) | ||
HMAC::SHA1.digest(@password,data) | ||
end | ||
|
||
# this is a translation of the helper function "F" defined in the spec | ||
def calculate_block(block_num) | ||
# u_1: | ||
u = prf(salt+[block_num].pack("N")) | ||
ret = u | ||
# u_2 through u_c: | ||
2.upto(@iterations) do | ||
# calculate u_n | ||
u = prf(u) | ||
# xor it with the previous results | ||
ret = ret^u | ||
end | ||
ret | ||
end | ||
|
||
# the bit that actually does the calculating | ||
def calculate! | ||
# how many blocks we'll need to calculate (the last may be truncated) | ||
blocks_needed = (@key_length.to_f / 20).ceil | ||
# reset | ||
@value = "" | ||
# main block-calculating loop: | ||
1.upto(blocks_needed) do |block_num| | ||
@value << calculate_block(block_num) | ||
end | ||
# truncate to desired length: | ||
@value = @value.slice(0,@key_length) | ||
@value | ||
end | ||
end | ||
|
||
class String | ||
if RUBY_VERSION >= "1.9" | ||
def xor_impl(other) | ||
result = "" | ||
o_bytes = other.bytes.to_a | ||
bytes.each_with_index do |c, i| | ||
result << (c ^ o_bytes[i]) | ||
end | ||
result | ||
end | ||
else | ||
def xor_impl(other) | ||
result = (0..self.length-1).collect { |i| self[i] ^ other[i] } | ||
result.pack("C*") | ||
end | ||
end | ||
|
||
private :xor_impl | ||
|
||
def ^(other) | ||
raise ArgumentError, "Can't bitwise-XOR a String with a non-String" \ | ||
unless other.kind_of? String | ||
raise ArgumentError, "Can't bitwise-XOR strings of different length" \ | ||
unless self.length == other.length | ||
|
||
xor_impl(other) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
require '../pbkdf2.rb' | ||
|
||
describe PBKDF2, "when deriving keys" do | ||
# see http://www.rfc-archive.org/getrfc.php?rfc=3962 | ||
# all examples there use HMAC-SHA1 | ||
it "should match the first test case in appendix B of RFC 3962" do | ||
# Iteration count = 1 | ||
# Pass phrase = "password" | ||
# Salt = "ATHENA.MIT.EDUraeburn" | ||
# 128-bit PBKDF2 output: | ||
# cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15 | ||
# 256-bit PBKDF2 output: | ||
# cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15 | ||
# 0a d1 f7 a0 4b b9 f3 a3 33 ec c0 e2 e1 f7 08 37 | ||
p = PBKDF2.new do |p| | ||
p.iterations = 1 | ||
p.password = "password" | ||
p.salt = "ATHENA.MIT.EDUraeburn" | ||
p.key_length = 128/8 | ||
end | ||
|
||
expected = "cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15" | ||
p.hex_string.should == expected.gsub(' ','') | ||
|
||
expected = "cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15" + | ||
"0a d1 f7 a0 4b b9 f3 a3 33 ec c0 e2 e1 f7 08 37" | ||
|
||
p.key_length = 256/8 | ||
p.hex_string.should == expected.gsub(' ','') | ||
end | ||
|
||
it "should match the second test case in appendix B of RFC 3962" do | ||
# Iteration count = 2 | ||
# Pass phrase = "password" | ||
# Salt="ATHENA.MIT.EDUraeburn" | ||
# 128-bit PBKDF2 output: | ||
# 01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d | ||
# 256-bit PBKDF2 output: | ||
# 01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d | ||
# a0 53 78 b9 32 44 ec 8f 48 a9 9e 61 ad 79 9d 86 | ||
p = PBKDF2.new do |p| | ||
p.iterations = 2 | ||
p.password = "password" | ||
p.salt = "ATHENA.MIT.EDUraeburn" | ||
p.key_length = 128/8 | ||
end | ||
|
||
expected = "01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d" | ||
p.hex_string.should == expected.gsub(' ','') | ||
|
||
expected = "01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d" + | ||
"a0 53 78 b9 32 44 ec 8f 48 a9 9e 61 ad 79 9d 86" | ||
p.key_length = 256/8 | ||
p.hex_string.should == expected.gsub(' ','') | ||
end | ||
|
||
it "should match the third test case in appendix B of RFC 3962" do | ||
# Iteration count = 1200 | ||
# Pass phrase = "password" | ||
# Salt = "ATHENA.MIT.EDUraeburn" | ||
# 128-bit PBKDF2 output: | ||
# 5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b | ||
# 256-bit PBKDF2 output: | ||
# 5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b | ||
# a7 e5 2d db c5 e5 14 2f 70 8a 31 e2 e6 2b 1e 13 | ||
p = PBKDF2.new do |p| | ||
p.iterations = 1200 | ||
p.password = "password" | ||
p.salt = "ATHENA.MIT.EDUraeburn" | ||
p.key_length = 128/8 | ||
end | ||
|
||
expected = "5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b" | ||
p.hex_string.should == expected.gsub(' ','') | ||
|
||
expected = "5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b" + | ||
"a7 e5 2d db c5 e5 14 2f 70 8a 31 e2 e6 2b 1e 13" | ||
p.key_length = 256/8 | ||
p.hex_string.should == expected.gsub(' ','') | ||
end | ||
|
||
it "should match the fourth test case in appendix B of RFC 3962" do | ||
# Iteration count = 5 | ||
# Pass phrase = "password" | ||
# Salt=0x1234567878563412 | ||
# 128-bit PBKDF2 output: | ||
# d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49 | ||
# 256-bit PBKDF2 output: | ||
# d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49 | ||
# 3f 98 d2 03 e6 be 49 a6 ad f4 fa 57 4b 6e 64 ee | ||
p = PBKDF2.new do |p| | ||
p.iterations = 5 | ||
p.password = "password" | ||
p.salt = [0x1234567878563412].pack("Q") | ||
p.key_length = 128/8 | ||
end | ||
|
||
expected = "d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49" | ||
p.hex_string.should == expected.gsub(' ','') | ||
|
||
expected = "d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49" + | ||
"3f 98 d2 03 e6 be 49 a6 ad f4 fa 57 4b 6e 64 ee" | ||
p.key_length = 256/8 | ||
p.hex_string.should == expected.gsub(' ','') | ||
end | ||
|
||
it "should match the fifth test case in appendix B of RFC 3962" do | ||
# Iteration count = 1200 | ||
# Pass phrase = (64 characters) | ||
# "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" | ||
# Salt="pass phrase equals block size" | ||
# 128-bit PBKDF2 output: | ||
# 13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9 | ||
# 256-bit PBKDF2 output: | ||
# 13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9 | ||
# c5 ec 59 f1 a4 52 f5 cc 9a d9 40 fe a0 59 8e d1 | ||
p = PBKDF2.new do |p| | ||
p.iterations = 1200 | ||
p.password = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" | ||
p.salt = "pass phrase equals block size" | ||
p.key_length = 128/8 | ||
end | ||
|
||
expected = "13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9" | ||
p.hex_string.should == expected.gsub(' ','') | ||
|
||
expected = "13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9" + | ||
"c5 ec 59 f1 a4 52 f5 cc 9a d9 40 fe a0 59 8e d1" | ||
p.key_length = 256/8 | ||
p.hex_string.should == expected.gsub(' ','') | ||
end | ||
|
||
it "should match the sixth test case in appendix B of RFC 3962" do | ||
# Iteration count = 1200 | ||
# Pass phrase = (65 characters) | ||
# "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" | ||
# Salt = "pass phrase exceeds block size" | ||
# 128-bit PBKDF2 output: | ||
# 9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61 | ||
# 256-bit PBKDF2 output: | ||
# 9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61 | ||
# 1a 8b 4d 28 26 01 db 3b 36 be 92 46 91 5e c8 2a | ||
p = PBKDF2.new do |p| | ||
p.iterations = 1200 | ||
p.password = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" | ||
p.salt = "pass phrase exceeds block size" | ||
p.key_length = 128/8 | ||
end | ||
|
||
expected = "9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61" | ||
p.hex_string.should == expected.gsub(' ','') | ||
|
||
expected = "9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61" + | ||
"1a 8b 4d 28 26 01 db 3b 36 be 92 46 91 5e c8 2a" | ||
p.key_length = 256/8 | ||
p.hex_string.should == expected.gsub(' ','') | ||
end | ||
|
||
it "should match the seventh test case in appendix B of RFC 3962" do | ||
# Iteration count = 50 | ||
# Pass phrase = g-clef (0xf09d849e) | ||
# Salt = "EXAMPLE.COMpianist" | ||
# 128-bit PBKDF2 output: | ||
# 6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39 | ||
# 256-bit PBKDF2 output: | ||
# 6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39 | ||
# e7 fe 37 a0 c4 1e 02 c2 81 ff 30 69 e1 e9 4f 52 | ||
p = PBKDF2.new do |p| | ||
p.iterations = 50 | ||
# this is a gorram horrible test case. it took me quite a while to | ||
# track down why 0xf09d849e should be interpreted as "\360\235\204\236" | ||
# (which is what other code uses for this example). the mysterious | ||
# "g-clef" annotation didn't help (turns out to be a Unicode character | ||
# in UTF8 -- ie, 0xf0 0x9d 0x84 0x9e) | ||
p.password = [0xf09d849e].pack("N") | ||
p.salt = "EXAMPLE.COMpianist" | ||
p.key_length = 128/8 | ||
end | ||
|
||
expected = "6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39" | ||
p.hex_string.should == expected.gsub(' ','') | ||
|
||
expected = "6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39" + | ||
"e7 fe 37 a0 c4 1e 02 c2 81 ff 30 69 e1 e9 4f 52" | ||
p.key_length = 256/8 | ||
p.hex_string.should == expected.gsub(' ','') | ||
end | ||
end |
...so we're still using a global salt? This still seems like a Bad Idea.