scrypt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ /*-
2
+ * Copyright 2005,2007,2009 Colin Percival
3
+ * All rights reserved.
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without
6
+ * modification, are permitted provided that the following conditions
7
+ * are met:
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ *
14
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
+ * SUCH DAMAGE.
25
+ *
26
+ * $FreeBSD: src/lib/libmd/sha256.h,v 1.2 2006/01/17 15:35:56 phk Exp $
27
+ */
28
+
29
+ #ifndef _SHA256_H_
30
+ #define _SHA256_H_
31
+
32
+ #include <sys/types.h>
33
+
34
+ #include <stdint.h>
35
+
36
+ typedef struct SHA256Context {
37
+ uint32_t state[8];
38
+ uint32_t count[2];
39
+ unsigned char buf[64];
40
+ } SHA256_CTX;
41
+
42
+ typedef struct HMAC_SHA256Context {
43
+ SHA256_CTX ictx;
44
+ SHA256_CTX octx;
45
+ } HMAC_SHA256_CTX;
46
+
47
+ void SHA256_Init(SHA256_CTX *);
48
+ void SHA256_Update(SHA256_CTX *, const void *, size_t);
49
+ void SHA256_Final(unsigned char [32], SHA256_CTX *);
50
+ void HMAC_SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t);
51
+ void HMAC_SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t);
52
+ void HMAC_SHA256_Final(unsigned char [32], HMAC_SHA256_CTX *);
53
+
54
+ /**
55
+ * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen):
56
+ * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and
57
+ * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1).
58
+ */
59
+ void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t,
60
+ uint64_t, uint8_t *, size_t);
61
+
62
+ #endif /* !_SHA256_H_ */
@@ -0,0 +1,140 @@
1
+ /*-
2
+ * Copyright 2007-2009 Colin Percival
3
+ * All rights reserved.
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without
6
+ * modification, are permitted provided that the following conditions
7
+ * are met:
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ *
14
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
+ * SUCH DAMAGE.
25
+ *
26
+ * This file was originally written by Colin Percival as part of the Tarsnap
27
+ * online backup system.
28
+ */
29
+ #ifndef _SYSENDIAN_H_
30
+ #define _SYSENDIAN_H_
31
+
32
+ #include "scrypt_platform.h"
33
+
34
+ /* If we don't have be64enc, the <sys/endian.h> we have isn't usable. */
35
+ #if !HAVE_DECL_BE64ENC
36
+ #undef HAVE_SYS_ENDIAN_H
37
+ #endif
38
+
39
+ #ifdef HAVE_SYS_ENDIAN_H
40
+
41
+ #include <sys/endian.h>
42
+
43
+ #else
44
+
45
+ #include <stdint.h>
46
+
47
+ static inline uint32_t
48
+ be32dec(const void *pp)
49
+ {
50
+ const uint8_t *p = (uint8_t const *)pp;
51
+
52
+ return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) +
53
+ ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24));
54
+ }
55
+
56
+ static inline void
57
+ be32enc(void *pp, uint32_t x)
58
+ {
59
+ uint8_t * p = (uint8_t *)pp;
60
+
61
+ p[3] = x & 0xff;
62
+ p[2] = (x >> 8) & 0xff;
63
+ p[1] = (x >> 16) & 0xff;
64
+ p[0] = (x >> 24) & 0xff;
65
+ }
66
+
67
+ static inline uint64_t
68
+ be64dec(const void *pp)
69
+ {
70
+ const uint8_t *p = (uint8_t const *)pp;
71
+
72
+ return ((uint64_t)(p[7]) + ((uint64_t)(p[6]) << 8) +
73
+ ((uint64_t)(p[5]) << 16) + ((uint64_t)(p[4]) << 24) +
74
+ ((uint64_t)(p[3]) << 32) + ((uint64_t)(p[2]) << 40) +
75
+ ((uint64_t)(p[1]) << 48) + ((uint64_t)(p[0]) << 56));
76
+ }
77
+
78
+ static inline void
79
+ be64enc(void *pp, uint64_t x)
80
+ {
81
+ uint8_t * p = (uint8_t *)pp;
82
+
83
+ p[7] = x & 0xff;
84
+ p[6] = (x >> 8) & 0xff;
85
+ p[5] = (x >> 16) & 0xff;
86
+ p[4] = (x >> 24) & 0xff;
87
+ p[3] = (x >> 32) & 0xff;
88
+ p[2] = (x >> 40) & 0xff;
89
+ p[1] = (x >> 48) & 0xff;
90
+ p[0] = (x >> 56) & 0xff;
91
+ }
92
+
93
+ static inline uint32_t
94
+ le32dec(const void *pp)
95
+ {
96
+ const uint8_t *p = (uint8_t const *)pp;
97
+
98
+ return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) +
99
+ ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24));
100
+ }
101
+
102
+ static inline void
103
+ le32enc(void *pp, uint32_t x)
104
+ {
105
+ uint8_t * p = (uint8_t *)pp;
106
+
107
+ p[0] = x & 0xff;
108
+ p[1] = (x >> 8) & 0xff;
109
+ p[2] = (x >> 16) & 0xff;
110
+ p[3] = (x >> 24) & 0xff;
111
+ }
112
+
113
+ static inline uint64_t
114
+ le64dec(const void *pp)
115
+ {
116
+ const uint8_t *p = (uint8_t const *)pp;
117
+
118
+ return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) +
119
+ ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) +
120
+ ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) +
121
+ ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56));
122
+ }
123
+
124
+ static inline void
125
+ le64enc(void *pp, uint64_t x)
126
+ {
127
+ uint8_t * p = (uint8_t *)pp;
128
+
129
+ p[0] = x & 0xff;
130
+ p[1] = (x >> 8) & 0xff;
131
+ p[2] = (x >> 16) & 0xff;
132
+ p[3] = (x >> 24) & 0xff;
133
+ p[4] = (x >> 32) & 0xff;
134
+ p[5] = (x >> 40) & 0xff;
135
+ p[6] = (x >> 48) & 0xff;
136
+ p[7] = (x >> 56) & 0xff;
137
+ }
138
+ #endif /* !HAVE_SYS_ENDIAN_H */
139
+
140
+ #endif /* !_SYSENDIAN_H_ */
@@ -0,0 +1,171 @@
1
+ # A wrapper for the scrypt algorithm.
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "ext", "mri")))
4
+ require "scrypt_ext"
5
+ require "openssl"
6
+ require "digest/sha1"
7
+ require "scanf"
8
+
9
+
10
+ module SCrypt
11
+ module Errors
12
+ class InvalidSalt < StandardError; end # The salt parameter provided is invalid.
13
+ class InvalidHash < StandardError; end # The hash parameter provided is invalid.
14
+ class InvalidSecret < StandardError; end # The secret parameter provided is invalid.
15
+ end
16
+
17
+ class Engine
18
+ DEFAULTS = {
19
+ :max_mem => 1024 * 1024,
20
+ :max_memfrac => 0.5,
21
+ :max_time => 0.2
22
+ }
23
+
24
+ private_class_method :__sc_calibrate
25
+ private_class_method :__sc_crypt
26
+
27
+ # Given a secret and a valid salt (see SCrypt::Engine.generate_salt) calculates an scrypt password hash.
28
+ def self.hash_secret(secret, salt)
29
+ if valid_secret?(secret)
30
+ if valid_salt?(salt)
31
+ cost = autodetect_cost(salt)
32
+ salt + "$" + Digest::SHA1.hexdigest(__sc_crypt(secret.to_s, salt, cost))
33
+ else
34
+ raise Errors::InvalidSalt.new("invalid salt")
35
+ end
36
+ else
37
+ raise Errors::InvalidSecret.new("invalid secret")
38
+ end
39
+ end
40
+
41
+ # Generates a random salt with a given computational cost.
42
+ def self.generate_salt(options = {})
43
+ options = DEFAULTS.merge(options)
44
+ cost = calibrate(options)
45
+ salt = Digest::SHA1.hexdigest(OpenSSL::Random.random_bytes(32))
46
+ cost + salt
47
+ end
48
+
49
+ # Returns true if +cost+ is a valid cost, false if not.
50
+ def self.valid_cost?(cost)
51
+ cost.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$$/) != nil
52
+ end
53
+
54
+ # Returns true if +salt+ is a valid salt, false if not.
55
+ def self.valid_salt?(salt)
56
+ salt.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{20,}$/) != nil
57
+ end
58
+
59
+ # Returns true if +secret+ is a valid secret, false if not.
60
+ def self.valid_secret?(secret)
61
+ secret.respond_to?(:to_s)
62
+ end
63
+
64
+ # Returns the cost value which will result in computation limits less than the given options.
65
+ #
66
+ # Example:
67
+ #
68
+ # # should take less than 200ms
69
+ # SCrypt.calibrate(:max_time => 0.2)
70
+ #
71
+ # # should take less than 1000ms
72
+ # SCrypt.calibrate(:max_time => 1)
73
+ #
74
+ def self.calibrate(options = {})
75
+ options = DEFAULTS.merge(options)
76
+ __sc_calibrate(options[:max_mem], options[:max_memfrac], options[:max_time])
77
+ end
78
+
79
+ # Computes the memory use of the given +cost+
80
+ def self.memory_use(cost)
81
+ n, r, p = cost.scanf("%x$%x$%x$")
82
+ (128 * r * p) + (256 * r) + (128 * r * n);
83
+ end
84
+
85
+ # Autodetects the cost from the salt string.
86
+ def self.autodetect_cost(salt)
87
+ salt[/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$/]
88
+ end
89
+ end
90
+
91
+ # A password management class which allows you to safely store users' passwords and compare them.
92
+ #
93
+ # Example usage:
94
+ #
95
+ # include "scrypt"
96
+ #
97
+ # # hash a user's password
98
+ # @password = Password.create("my grand secret")
99
+ # @password #=> "2000$8$1$f5f2fa5fe5484a7091f1299768fbe92b5a7fbc77$6a385f22c54d92c314b71a4fd5ef33967c93d679"
100
+ #
101
+ # # store it safely
102
+ # @user.update_attribute(:password, @password)
103
+ #
104
+ # # read it back
105
+ # @user.reload!
106
+ # @db_password = Password.new(@user.password)
107
+ #
108
+ # # compare it after retrieval
109
+ # @db_password == "my grand secret" #=> true
110
+ # @db_password == "a paltry guess" #=> false
111
+ #
112
+ class Password < String
113
+ # The hash portion of the stored password hash.
114
+ attr_reader :hash
115
+ # The salt of the store password hash
116
+ attr_reader :salt
117
+ # The cost factor used to create the hash.
118
+ attr_reader :cost
119
+
120
+ class << self
121
+ # Hashes a secret, returning a SCrypt::Password instance.
122
+ # Takes three options (optional), which will determine the cost limits of the computation.
123
+ # <tt>:max_time</tt> specifies the maximum number of seconds the computation should take.
124
+ # <tt>:max_mem</tt> specifies the maximum number of bytes the computation should take. A value of 0 specifies no upper limit. The minimum is always 1 MB.
125
+ # <tt>:max_memfrac</tt> specifies the maximum memory in a fraction of available resources to use. Any value equal to 0 or greater than 0.5 will result in 0.5 being used.
126
+ # The scrypt key derivation function is designed to be far more secure against hardware brute-force attacks than alternative functions such as PBKDF2 or bcrypt.
127
+ # The designers of scrypt estimate that on modern (2009) hardware, if 5 seconds are spent computing a derived key, the cost of a hardware brute-force attack against scrypt is roughly 4000 times greater than the cost of a similar attack against bcrypt (to find the same password), and 20000 times greater than a similar attack against PBKDF2.
128
+ # Default options will result in calculation time of approx. 200 ms with 1 MB memory use.
129
+ #
130
+ # Example:
131
+ # @password = SCrypt::Password.create("my secret", :max_time => 0.25)
132
+ def create(secret, options = {})
133
+ options = SCrypt::Engine::DEFAULTS.merge(options)
134
+ salt = SCrypt::Engine.generate_salt(options)
135
+ hash = SCrypt::Engine.hash_secret(secret, salt)
136
+ Password.new(hash)
137
+ end
138
+ end
139
+
140
+ # Initializes a SCrypt::Password instance with the data from a stored hash.
141
+ def initialize(raw_hash)
142
+ if valid_hash?(raw_hash)
143
+ self.replace(raw_hash)
144
+ @cost, @salt, @hash = split_hash(self.to_s)
145
+ else
146
+ raise Errors::InvalidHash.new("invalid hash")
147
+ end
148
+ end
149
+
150
+ # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
151
+ def ==(secret)
152
+ super(SCrypt::Engine.hash_secret(secret, @cost + @salt))
153
+ end
154
+ alias_method :is_password?, :==
155
+
156
+ private
157
+ # Returns true if +h+ is a valid hash.
158
+ def valid_hash?(h)
159
+ h.match(/^[0-9a-z]+\$[0-9a-z]+\$[0-9a-z]+\$[A-Za-z0-9]{20,}\$[A-Za-z0-9]{20,}$/) != nil
160
+ end
161
+
162
+ # call-seq:
163
+ # split_hash(raw_hash) -> cost, salt, hash
164
+ #
165
+ # Splits +h+ into cost, salt, and hash and returns them in that order.
166
+ def split_hash(h)
167
+ n, v, r, salt, hash = h.split('$')
168
+ return [n, v, r].join('$') + "$", salt, hash
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,3 @@
1
+ module SCrypt
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "scrypt/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "scrypt"
7
+ s.version = SCrypt::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Patrick Hogan"]
10
+ s.email = ["pbhogan@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = "scrypt password hashing algorithm."
13
+ s.description = <<-EOF
14
+ The scrypt key derivation function is designed to be far
15
+ more secure against hardware brute-force attacks than
16
+ alternative functions such as PBKDF2 or bcrypt.
17
+ EOF
18
+
19
+ s.rubyforge_project = "scrypt"
20
+
21
+ s.extensions = ["ext/mri/extconf.rb"]
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ describe "The SCrypt engine" do
4
+ it "should calculate a valid cost factor" do
5
+ first = SCrypt::Engine.calibrate(:max_time => 0.2)
6
+ SCrypt::Engine.valid_cost?(first).should == true
7
+ end
8
+ end
9
+
10
+ describe "Generating SCrypt salts" do
11
+
12
+ it "should produce strings" do
13
+ SCrypt::Engine.generate_salt.should be_an_instance_of(String)
14
+ end
15
+
16
+ it "should produce random data" do
17
+ SCrypt::Engine.generate_salt.should_not equal(SCrypt::Engine.generate_salt)
18
+ end
19
+
20
+ end
21
+
22
+ describe "Autodetecting of salt cost" do
23
+
24
+ it "should work" do
25
+ SCrypt::Engine.autodetect_cost("2a$08$c3$randomjunkgoeshere").should == "2a$08$c3$"
26
+ end
27
+
28
+ end
29
+
30
+ describe "Generating SCrypt hashes" do
31
+
32
+ class MyInvalidSecret
33
+ undef to_s
34
+ end
35
+
36
+ before :each do
37
+ @salt = SCrypt::Engine.generate_salt()
38
+ @password = "woo"
39
+ end
40
+
41
+ it "should produce a string" do
42
+ SCrypt::Engine.hash_secret(@password, @salt).should be_an_instance_of(String)
43
+ end
44
+
45
+ it "should raise an InvalidSalt error if the salt is invalid" do
46
+ lambda { SCrypt::Engine.hash_secret(@password, 'nino') }.should raise_error(SCrypt::Errors::InvalidSalt)
47
+ end
48
+
49
+ it "should raise an InvalidSecret error if the secret is invalid" do
50
+ lambda { SCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }.should raise_error(SCrypt::Errors::InvalidSecret)
51
+ lambda { SCrypt::Engine.hash_secret(nil, @salt) }.should_not raise_error(SCrypt::Errors::InvalidSecret)
52
+ lambda { SCrypt::Engine.hash_secret(false, @salt) }.should_not raise_error(SCrypt::Errors::InvalidSecret)
53
+ end
54
+
55
+ it "should call #to_s on the secret and use the return value as the actual secret data" do
56
+ SCrypt::Engine.hash_secret(false, @salt).should == SCrypt::Engine.hash_secret("false", @salt)
57
+ end
58
+ end