An unofficial, mostly Bitwarden-compatible API server written in Ruby (Sinatra and ActiveRecord)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add support for per-user KDF iteration counts

Before logging in, Bitwarden clients now POST to
$baseURL/accounts/prelogin asking for the KdfIterations of the
account given.

I'm not sure what the 'Kdf:0' parameter is though, so ignore it for
now.

Issue #66

+160 -66
+39 -17
API.md
··· 21 21 22 22 Overview: 23 23 24 - - PBKDF2 with 5000 rounds stretches the user's master password with a salt 25 - of the user's e-mail address to become the master key (unknown to the 26 - server). 24 + - PBKDF2 with `$KdfIterations` rounds stretches the user's master password 25 + with a salt of the user's e-mail address to become the master key (unknown 26 + to the server). 27 27 - 64 random bytes are generated to become the symmetric key, the first half 28 28 of which becomes the encryption key and the second half becomes the MAC key. 29 29 - The master key and a random 16-byte IV are used to encrypt the symmetric ··· 47 47 `nobody@example.com`. 48 48 49 49 PBKDF2 is used with a password of `$masterPassword`, salt of lowercased 50 - `$email`, and 5000 iterations to stretch password into `$masterKey`. 50 + `$email`, and `$iterations` KDF iterations to stretch password into 51 + `$masterKey`. 51 52 52 - def makeKey(password, salt) 53 + def makeKey(password, salt, iterations) 53 54 PBKDF2.new(:password => password, :salt => salt, 54 - :iterations => 5000, :hash_function => OpenSSL::Digest::SHA256, 55 + :iterations => iterations, :hash_function => OpenSSL::Digest::SHA256, 55 56 :key_length => (256 / 8)).bin_string 56 57 end 57 58 58 - irb> $masterKey = makeKey("p4ssw0rd", "nobody@example.com".downcase) 59 + irb> $masterKey = makeKey("p4ssw0rd", "nobody@example.com".downcase, 5000) 59 60 => "\x13\x88j`\x99m\xE3FA\x94\xEE'\xF0\xB2\x1A!\xB6>\\)\xF4\xD5\xCA#\xE5\e\xA6f5o{\xAA" 60 61 61 62 A random, 64-byte key `$symmetricKey` is created to become the symmetric key. ··· 104 105 `$email`)) and salt of `$masterPassword`. 105 106 106 107 # base64-encode a wrapped, stretched password+salt for signup/login 107 - def hashedPassword(password, salt) 108 - key = makeKey(password, salt) 108 + def hashedPassword(password, salt, kdf_iterations) 109 + key = makeKey(password, salt, kdf_iterations) 109 110 Base64.strict_encode64(PBKDF2.new(:password => key, :salt => password, 110 111 :iterations => 1, :key_length => 256/8, 111 112 :hash_function => OpenSSL::Digest::SHA256).bin_string) 112 113 end 113 114 114 - irb> $masterPasswordHash = hashedPassword("p4ssw0rd", "nobody@example.com") 115 + irb> $masterPasswordHash = hashedPassword("p4ssw0rd", "nobody@example.com", 5000) 115 116 => "r5CFRR+n9NQI8a525FY+0BPR0HGOjVJX0cR1KEMnIOo=" 116 117 117 118 Upon future logins with the user's plain-text `$masterPassword` and `$email`, ··· 240 241 Collect an e-mail address and master password, calculate `$internalKey`, 241 242 `$masterPasswordHash`, and the `$key` CipherString from the two values: 242 243 243 - irb> $internalKey = makeKey("p4ssw0rd", "nobody@example.com".downcase) 244 + irb> $internalKey = makeKey("p4ssw0rd", "nobody@example.com".downcase, 5000) 244 245 => "\x13\x88j`\x99m\xE3FA\x94\xEE'\xF0\xB2\x1A!\xB6>\\)\xF4\xD5\xCA#\xE5\e\xA6f5o{\xAA" 245 246 246 - irb> $masterPasswordHash = hashedPassword("p4ssw0rd", "nobody@example.com") 247 + irb> $masterPasswordHash = hashedPassword("p4ssw0rd", "nobody@example.com", 5000) 247 248 => "r5CFRR+n9NQI8a525FY+0BPR0HGOjVJX0cR1KEMnIOo=" 248 249 249 250 irb> $key = makeEncKey($internalKey) ··· 253 254 the next login. 254 255 255 256 Issue a `POST` to `$baseURL/accounts/register` with a JSON body containing the 256 - e-mail address, `$masterPasswordHash`, and `$key` (_not $internalKey_!): 257 + e-mail address, `$masterPasswordHash`, KDF iteration count `$kdfIterations`, 258 + and `$key` (_not $internalKey_!): 257 259 258 260 POST $baseURL/accounts/register 259 261 Content-type: application/json ··· 264 266 "masterPasswordHash": "r5CFRR+n9NQI8a525FY+0BPR0HGOjVJX0cR1KEMnIOo=", 265 267 "masterPasswordHint": null, 266 268 "key": "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=", 269 + "kdf": 0, 270 + "kdfIterations": 5000, 267 271 } 268 272 269 273 The response should be a `200` with a zero-byte body. 270 274 271 275 ### Login 272 276 273 - Collect an e-mail address and master password, and calculate 274 - `$internalKey` and `$masterPasswordHash` from the two values: 277 + Collect an e-mail address and master password, and issue a `POST` to 278 + `$baseURL/accounts/prelogin` to determine the KDF iterations for the given 279 + e-mail address: 275 280 276 - irb> $internalKey = makeKey("p4ssw0rd", "nobody@example.com".downcase) 281 + POST $baseURL/accounts/prelogin 282 + Content-type: application/json 283 + 284 + { 285 + "email": "nobody@example.com", 286 + } 287 + 288 + The `prelogin` response will contain the KDF iteration count: 289 + 290 + { 291 + "Kdf": 0, 292 + "KdfIterations": 5000, 293 + } 294 + 295 + With the KDF iteration count known, calculate `$internalKey` and 296 + `$masterPasswordHash` from the three values: 297 + 298 + irb> $internalKey = makeKey("p4ssw0rd", "nobody@example.com".downcase, 5000) 277 299 => "\x13\x88j`\x99m\xE3FA\x94\xEE'\xF0\xB2\x1A!\xB6>\\)\xF4\xD5\xCA#\xE5\e\xA6f5o{\xAA" 278 300 279 - irb> $masterPasswordHash = hashedPassword("p4ssw0rd", "nobody@example.com") 301 + irb> $masterPasswordHash = hashedPassword("p4ssw0rd", "nobody@example.com", 5000) 280 302 => "r5CFRR+n9NQI8a525FY+0BPR0HGOjVJX0cR1KEMnIOo=" 281 303 282 304 Securely erase the master password from memory, as it is no longer needed
+2
Rakefile
··· 1 1 require "rake/testtask" 2 + 3 + # rake db:create_migration NAME=... 2 4 require "sinatra/activerecord/rake" 3 5 4 6 Rake::TestTask.new do |t|
+10
db/migrate/20180818201731_user_kdf_iterations.rb
··· 1 + class UserKdfIterations < ActiveRecord::Migration[5.1] 2 + def change 3 + add_column :users, :kdf_iterations, :integer 4 + 5 + User.all.each do |u| 6 + u.kdf_iterations = User::DEFAULT_KDF_ITERATIONS 7 + u.save! 8 + end 9 + end 10 + end
+5 -4
lib/bitwarden.rb
··· 25 25 # just so we can test against 26 26 class << self 27 27 # pbkdf2 stretch a password+salt 28 - def makeKey(password, salt) 28 + def makeKey(password, salt, kdf_iterations) 29 29 PBKDF2.new(:password => password, :salt => salt, 30 - :iterations => 5000, :hash_function => OpenSSL::Digest::SHA256, 30 + :iterations => kdf_iterations, 31 + :hash_function => OpenSSL::Digest::SHA256, 31 32 :key_length => (256 / 8)).bin_string 32 33 end 33 34 ··· 51 52 end 52 53 53 54 # base64-encode a wrapped, stretched password+salt for signup/login 54 - def hashPassword(password, salt) 55 - key = makeKey(password, salt) 55 + def hashPassword(password, salt, kdf_iterations) 56 + key = makeKey(password, salt, kdf_iterations) 56 57 Base64.strict_encode64(PBKDF2.new(:password => key, :salt => password, 57 58 :iterations => 1, :key_length => 256/8, 58 59 :hash_function => OpenSSL::Digest::SHA256).bin_string)
+19 -1
lib/routes/api.rb
··· 19 19 module Api 20 20 def self.registered(app) 21 21 app.namespace BASE_URL do 22 + post "/accounts/prelogin" do 23 + need_params(:email) do |p| 24 + return validation_error("#{p} cannot be blank") 25 + end 26 + 27 + iterations = User::DEFAULT_KDF_ITERATIONS 28 + 29 + if u = User.find_by_email(params[:email]) 30 + iterations = u.kdf_iterations 31 + end 32 + 33 + { 34 + "Kdf" => 0, 35 + "KdfIterations" => iterations, 36 + }.to_json 37 + end 38 + 22 39 # create a new user 23 40 post "/accounts/register" do 24 41 content_type :json ··· 27 44 return validation_error("Signups are not permitted") 28 45 end 29 46 30 - need_params(:masterpasswordhash) do |p| 47 + need_params(:masterpasswordhash, :kdfiterations) do |p| 31 48 return validation_error("#{p} cannot be blank") 32 49 end 33 50 ··· 57 74 u.password_hash = params[:masterpasswordhash] 58 75 u.password_hint = params[:masterpasswordhint] 59 76 u.key = params[:key] 77 + u.kdf_iterations = params[:kdfiterations] 60 78 61 79 # is this supposed to come from somewhere? 62 80 u.culture = "en-US"
+6 -3
lib/user.rb
··· 20 20 self.table_name = "users" 21 21 #set_primary_key "uuid" 22 22 23 + DEFAULT_KDF_ITERATIONS = 5000 24 + 23 25 before_create :generate_uuid_primary_key 24 26 before_validation :generate_security_stamp 25 27 ··· 74 76 # a new key derived from the new password 75 77 76 78 orig_key = Bitwarden.decrypt(self.key, 77 - Bitwarden.makeKey(old_pwd, self.email), nil) 79 + Bitwarden.makeKey(old_pwd, self.email, self.kdf_iterations), nil) 78 80 79 81 self.key = Bitwarden.encrypt(orig_key, 80 - Bitwarden.makeKey(new_pwd, self.email)).to_s 82 + Bitwarden.makeKey(new_pwd, self.email, self.kdf_iterations)).to_s 81 83 82 - self.password_hash = Bitwarden.hashPassword(new_pwd, self.email) 84 + self.password_hash = Bitwarden.hashPassword(new_pwd, self.email, 85 + self.kdf_iterations) 83 86 self.security_stamp = SecureRandom.uuid 84 87 end 85 88
+9 -4
spec/cipher_spec.rb
··· 7 7 post "/api/accounts/register", { 8 8 :name => nil, 9 9 :email => "api@example.com", 10 - :masterPasswordHash => Bitwarden.hashPassword("asdf", "api@example.com"), 10 + :masterPasswordHash => Bitwarden.hashPassword("asdf", "api@example.com", 11 + User::DEFAULT_KDF_ITERATIONS), 11 12 :masterPasswordHint => nil, 12 13 :key => Bitwarden.makeEncKey( 13 - Bitwarden.makeKey("adsf", "api@example.com") 14 + Bitwarden.makeKey("adsf", "api@example.com", 15 + User::DEFAULT_KDF_ITERATIONS) 14 16 ), 17 + :kdf => 0, 18 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS, 15 19 } 16 20 17 21 post "/identity/connect/token", { 18 22 :grant_type => "password", 19 23 :username => "api@example.com", 20 - :password => Bitwarden.hashPassword("asdf", "api@example.com"), 24 + :password => Bitwarden.hashPassword("asdf", "api@example.com", 25 + User::DEFAULT_KDF_ITERATIONS), 21 26 :scope => "api offline_access", 22 27 :client_id => "browser", 23 28 :deviceType => 3, ··· 79 84 80 85 # update 81 86 82 - ik = Bitwarden.makeKey("asdf", "api@example.com") 87 + ik = Bitwarden.makeKey("asdf", "api@example.com", User::DEFAULT_KDF_ITERATIONS) 83 88 k = Bitwarden.makeEncKey(ik) 84 89 new_name = Bitwarden.encrypt("some new name", k[0, 32], k[32, 32]).to_s 85 90
+7 -7
spec/cipherstring_spec.rb
··· 4 4 it "should make a key from a password and salt" do 5 5 b64 = "2K4YP5Om9r5NpA7FCS4vQX5t+IC4hKYdTJN/C20cz9c=" 6 6 7 - k = Bitwarden.makeKey("this is a password", "nobody@example.com") 7 + k = Bitwarden.makeKey("this is a password", "nobody@example.com", 5000) 8 8 Base64.strict_encode64(k).encode("utf-8").must_equal b64 9 9 10 10 # make sure key and salt affect it 11 - k = Bitwarden.makeKey("this is a password", "nobody2@example.com") 11 + k = Bitwarden.makeKey("this is a password", "nobody2@example.com", 5000) 12 12 Base64.strict_encode64(k).encode("utf-8").wont_equal b64 13 13 14 - k = Bitwarden.makeKey("this is A password", "nobody@example.com") 14 + k = Bitwarden.makeKey("this is A password", "nobody@example.com", 5000) 15 15 Base64.strict_encode64(k).encode("utf-8").wont_equal b64 16 16 end 17 17 18 18 it "should make a cipher string from a key" do 19 19 cs = Bitwarden.makeEncKey( 20 - Bitwarden.makeKey("this is a password", "nobody@example.com") 20 + Bitwarden.makeKey("this is a password", "nobody@example.com", 5000) 21 21 ) 22 22 23 23 cs.must_match(/^0\.[^|]+|[^|]+$/) ··· 25 25 26 26 it "should hash a password" do 27 27 #def hashedPassword(password, salt) 28 - Bitwarden.hashPassword("secret password", "user@example.com"). 28 + Bitwarden.hashPassword("secret password", "user@example.com", 5000). 29 29 must_equal "VRlYxg0x41v40mvDNHljqpHcqlIFwQSzegeq+POW1ww=" 30 30 end 31 31 ··· 46 46 end 47 47 48 48 it "should encrypt and decrypt properly" do 49 - ik = Bitwarden.makeKey("password", "user@example.com") 49 + ik = Bitwarden.makeKey("password", "user@example.com", 5000) 50 50 ek = Bitwarden.makeEncKey(ik) 51 51 k = Bitwarden.decrypt(ek, ik, nil) 52 52 j = Bitwarden.encrypt("hi there", k[0, 32], k[32, 32]) 53 53 54 54 cs = Bitwarden::CipherString.parse(j) 55 55 56 - ik = Bitwarden.makeKey("password", "user@example.com") 56 + ik = Bitwarden.makeKey("password", "user@example.com", 5000) 57 57 Bitwarden.decrypt(cs.to_s, k[0, 32], k[32, 32]).must_equal "hi there" 58 58 end 59 59
+3 -2
spec/db_spec.rb
··· 8 8 9 9 u = User.new 10 10 u.email = "#{rand}@#{rand}.com" 11 - u.password_hash = Bitwarden.hashPassword("blah", u.email) 11 + u.password_hash = Bitwarden.hashPassword("blah", u.email, 12 + User::DEFAULT_KDF_ITERATIONS) 12 13 u.password_hint = nil 13 14 u.key = Bitwarden.makeEncKey( 14 - Bitwarden.makeKey("blah", u.email), 15 + Bitwarden.makeKey("blah", u.email, User::DEFAULT_KDF_ITERATIONS), 15 16 ) 16 17 u.culture = "en-US" 17 18 u.save.must_equal true
+8 -4
spec/folder_spec.rb
··· 7 7 post "/api/accounts/register", { 8 8 :name => nil, 9 9 :email => "api@example.com", 10 - :masterPasswordHash => Bitwarden.hashPassword("asdf", "api@example.com"), 10 + :masterPasswordHash => Bitwarden.hashPassword("asdf", "api@example.com", 11 + User::DEFAULT_KDF_ITERATIONS), 11 12 :masterPasswordHint => nil, 12 13 :key => Bitwarden.makeEncKey( 13 - Bitwarden.makeKey("adsf", "api@example.com") 14 + Bitwarden.makeKey("adsf", "api@example.com", User::DEFAULT_KDF_ITERATIONS), 14 15 ), 16 + :kdf => 0, 17 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS, 15 18 } 16 19 17 20 post "/identity/connect/token", { 18 21 :grant_type => "password", 19 22 :username => "api@example.com", 20 - :password => Bitwarden.hashPassword("asdf", "api@example.com"), 23 + :password => Bitwarden.hashPassword("asdf", "api@example.com", 24 + User::DEFAULT_KDF_ITERATIONS), 21 25 :scope => "api offline_access", 22 26 :client_id => "browser", 23 27 :deviceType => 3, ··· 57 61 58 62 # update 59 63 60 - ik = Bitwarden.makeKey("asdf", "api@example.com") 64 + ik = Bitwarden.makeKey("asdf", "api@example.com", User::DEFAULT_KDF_ITERATIONS) 61 65 k = Bitwarden.makeEncKey(ik) 62 66 new_name = Bitwarden.encrypt("some new name", k[0, 32], k[32, 32]).to_s 63 67
+31 -12
spec/identity_spec.rb
··· 6 6 :name => nil, 7 7 :email => "nobody@example.com", 8 8 :masterPasswordHash => Bitwarden.hashPassword("asdf", 9 - "nobody@example.com"), 9 + "nobody@example.com", User::DEFAULT_KDF_ITERATIONS), 10 10 :masterPasswordHint => nil, 11 11 :key => Bitwarden.makeEncKey( 12 - Bitwarden.makeKey("adsf", "nobody@example.com") 12 + Bitwarden.makeKey("adsf", "nobody@example.com", 13 + User::DEFAULT_KDF_ITERATIONS) 13 14 ), 15 + :kdf => 0, 16 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS 14 17 } 15 18 last_response.status.must_equal 200 16 19 end ··· 21 24 :name => nil, 22 25 :email => "nobody2@example.com", 23 26 :masterPasswordHash => Bitwarden.hashPassword("asdf", 24 - "nobody2@example.com"), 27 + "nobody2@example.com", User::DEFAULT_KDF_ITERATIONS), 25 28 :masterPasswordHint => nil, 26 29 :key => Bitwarden.makeEncKey( 27 - Bitwarden.makeKey("adsf", "nobody2@example.com") 30 + Bitwarden.makeKey("adsf", "nobody2@example.com", 31 + User::DEFAULT_KDF_ITERATIONS) 28 32 ), 33 + :kdf => 0, 34 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS, 29 35 } 30 36 if x == 0 31 37 last_response.status.must_equal 200 ··· 42 48 :masterPasswordHash => "", 43 49 :masterPasswordHint => nil, 44 50 :key => Bitwarden.makeEncKey( 45 - Bitwarden.makeKey("adsf", "nobody3@example.com") 51 + Bitwarden.makeKey("adsf", "nobody3@example.com", 52 + User::DEFAULT_KDF_ITERATIONS) 46 53 ), 54 + :kdf => 0, 55 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS, 47 56 } 48 57 last_response.status.wont_equal 200 49 58 ··· 51 60 :name => nil, 52 61 :email => "nobody3@example.com", 53 62 :masterPasswordHash => Bitwarden.hashPassword("asdf", 54 - "nobody3@example.com"), 63 + "nobody3@example.com", User::DEFAULT_KDF_ITERATIONS), 55 64 :masterPasswordHint => nil, 56 65 :key => "junk", 66 + :kdf => 0, 67 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS, 57 68 } 58 69 last_response.status.wont_equal 200 59 70 end ··· 63 74 :name => nil, 64 75 :email => "nobody4@example.com", 65 76 :masterPasswordHash => Bitwarden.hashPassword("asdf", 66 - "nobody4@example.com"), 77 + "nobody4@example.com", User::DEFAULT_KDF_ITERATIONS), 67 78 :masterPasswordHint => nil, 68 79 :key => Bitwarden.makeEncKey( 69 - Bitwarden.makeKey("adsf", "nobody4@example.com") 80 + Bitwarden.makeKey("adsf", "nobody4@example.com", 81 + User::DEFAULT_KDF_ITERATIONS) 70 82 ), 83 + :kdf => 0, 84 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS, 71 85 } 72 86 last_response.status.must_equal 200 73 87 ··· 78 92 post "/identity/connect/token", { 79 93 :grant_type => "password", 80 94 :username => "nobody4@example.com", 81 - :password => Bitwarden.hashPassword("asdf", "nobody4@example.com"), 95 + :password => Bitwarden.hashPassword("asdf", "nobody4@example.com", 96 + User::DEFAULT_KDF_ITERATIONS), 82 97 :scope => "api offline_access", 83 98 :client_id => "browser", 84 99 :deviceType => 3, ··· 101 116 :name => nil, 102 117 :email => "nobody5@example.com", 103 118 :masterPasswordHash => Bitwarden.hashPassword("asdf", 104 - "nobody5@example.com"), 119 + "nobody5@example.com", User::DEFAULT_KDF_ITERATIONS), 105 120 :masterPasswordHint => nil, 106 121 :key => Bitwarden.makeEncKey( 107 - Bitwarden.makeKey("adsf", "nobody5@example.com") 122 + Bitwarden.makeKey("adsf", "nobody5@example.com", 123 + User::DEFAULT_KDF_ITERATIONS) 108 124 ), 125 + :kdf => 0, 126 + :kdfIterations => User::DEFAULT_KDF_ITERATIONS, 109 127 } 110 128 last_response.status.must_equal 200 111 129 112 130 post "/identity/connect/token", { 113 131 :grant_type => "password", 114 132 :username => "nobody5@example.com", 115 - :password => Bitwarden.hashPassword("asdf", "nobody5@example.com"), 133 + :password => Bitwarden.hashPassword("asdf", "nobody5@example.com", 134 + User::DEFAULT_KDF_ITERATIONS), 116 135 :scope => "api offline_access", 117 136 :client_id => "browser", 118 137 :deviceType => 3,
+17 -8
spec/user_spec.rb
··· 9 9 10 10 u = User.new 11 11 u.email = USER_EMAIL 12 - u.password_hash = Bitwarden.hashPassword(USER_PASSWORD, USER_EMAIL) 12 + u.kdf_iterations = User::DEFAULT_KDF_ITERATIONS 13 + u.password_hash = Bitwarden.hashPassword(USER_PASSWORD, USER_EMAIL, 14 + u.kdf_iterations) 13 15 u.password_hint = "it's like password but not" 14 - u.key = Bitwarden.makeEncKey(Bitwarden.makeKey(USER_PASSWORD, USER_EMAIL)) 16 + u.key = Bitwarden.makeEncKey(Bitwarden.makeKey(USER_PASSWORD, USER_EMAIL, 17 + u.kdf_iterations)) 15 18 u.save 16 19 end 17 20 ··· 19 22 u = User.find_by_email(USER_EMAIL) 20 23 u.email.must_equal USER_EMAIL 21 24 u.has_password_hash?( 22 - Bitwarden.hashPassword(USER_PASSWORD, USER_EMAIL)).must_equal true 25 + Bitwarden.hashPassword(USER_PASSWORD, USER_EMAIL, 26 + User::DEFAULT_KDF_ITERATIONS)).must_equal true 23 27 24 28 u.has_password_hash?( 25 - Bitwarden.hashPassword(USER_PASSWORD, USER_EMAIL + "2")).wont_equal true 29 + Bitwarden.hashPassword(USER_PASSWORD, USER_EMAIL + "2", 30 + User::DEFAULT_KDF_ITERATIONS)).wont_equal true 26 31 end 27 32 28 33 it "encrypts and decrypts user's ciphers" do 29 34 u = User.find_by_email(USER_EMAIL) 30 35 31 - mk = Bitwarden.makeKey(USER_PASSWORD, USER_EMAIL) 36 + mk = Bitwarden.makeKey(USER_PASSWORD, USER_EMAIL, 37 + User::DEFAULT_KDF_ITERATIONS) 32 38 33 39 c = Cipher.new 34 40 c.user_uuid = u.uuid ··· 49 55 it "supports changing a master password" do 50 56 u = User.find_by_email(USER_EMAIL) 51 57 52 - mk = Bitwarden.makeKey(USER_PASSWORD, USER_EMAIL) 58 + mk = Bitwarden.makeKey(USER_PASSWORD, USER_EMAIL, 59 + User::DEFAULT_KDF_ITERATIONS) 53 60 54 61 c = Cipher.new 55 62 c.user_uuid = u.uuid ··· 67 74 post "/identity/connect/token", { 68 75 :grant_type => "password", 69 76 :username => USER_EMAIL, 70 - :password => Bitwarden.hashPassword(USER_PASSWORD + "2", USER_EMAIL), 77 + :password => Bitwarden.hashPassword(USER_PASSWORD + "2", USER_EMAIL, 78 + User::DEFAULT_KDF_ITERATIONS), 71 79 :scope => "api offline_access", 72 80 :client_id => "browser", 73 81 :deviceType => 3, ··· 77 85 } 78 86 last_response.status.must_equal 200 79 87 80 - mk = Bitwarden.makeKey(USER_PASSWORD + "2", USER_EMAIL) 88 + mk = Bitwarden.makeKey(USER_PASSWORD + "2", USER_EMAIL, 89 + User::DEFAULT_KDF_ITERATIONS) 81 90 82 91 c = Cipher.find_by_uuid(c.uuid) 83 92 u.decrypt_data_with_master_password_key(c.to_hash["Name"], mk).
+1 -1
tools/1password_import.rb
··· 74 74 raise "master password does not match stored hash" 75 75 end 76 76 77 - @master_key = Bitwarden.makeKey(password, @u.email) 77 + @master_key = Bitwarden.makeKey(password, @u.email, @u.kdf_iterations) 78 78 79 79 def encrypt(str) 80 80 @u.encrypt_data_with_master_password_key(str, @master_key)
+1 -1
tools/bitwarden_import.rb
··· 88 88 raise 'master password does not match stored hash' 89 89 end 90 90 91 - @master_key = Bitwarden.makeKey(password, @u.email) 91 + @master_key = Bitwarden.makeKey(password, @u.email, @u.kdf_iterations) 92 92 93 93 @u.folders.each do |folder| 94 94 folder_name = @u.decrypt_data_with_master_password_key(folder.name, @master_key)
+1 -1
tools/keepass_import.rb
··· 81 81 raise "master password does not match stored hash" 82 82 end 83 83 84 - @master_key = Bitwarden.makeKey(password, @u.email) 84 + @master_key = Bitwarden.makeKey(password, @u.email, @u.kdf_iterations) 85 85 86 86 @u.folders.each do |folder| 87 87 folder_name = @u.decrypt_data_with_master_password_key(folder.name, @master_key)
+1 -1
tools/lastpass_import.rb
··· 76 76 raise "master password does not match stored hash" 77 77 end 78 78 79 - @master_key = Bitwarden.makeKey(password, @u.email) 79 + @master_key = Bitwarden.makeKey(password, @u.email, @u.kdf_iterations) 80 80 81 81 @u.folders.each do |folder| 82 82 folder_name = @u.decrypt_data_with_master_password_key(folder.name, @master_key)