Skip to content

Commit

Permalink
Fix #1717: Add support for bcrypt to store the password (#1718)
Browse files Browse the repository at this point in the history
  • Loading branch information
romanstrobl authored Oct 18, 2024
1 parent 7fba1fb commit 558f13f
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
*/
package io.getlime.security.powerauth.lib.nextstep.model.entity.enumeration;

import lombok.Getter;

/**
* Enumeration representing hashing algorithms.
*
* @author Roman Strobl, [email protected]
*/
@Getter
public enum HashAlgorithm {

/**
Expand All @@ -37,35 +40,30 @@ public enum HashAlgorithm {
/**
* Algorithm argon2id.
*/
ARGON_2ID("argon2id", 2);
ARGON_2ID("argon2id", 2),

BCRYPT("bcrypt");

private final String name;
private final int id;
private final Integer id;

/**
* Hash algorithm constructor.
* @param name Algorithm name for Modular Crypt Format.
* @param id Algorithm ID in Bouncy Castle library.
* @param name Algorithm name.
*/
HashAlgorithm(String name, int id) {
HashAlgorithm(String name) {
this.name = name;
this.id = id;
this.id = null;
}

/**
* Get algorithm name for Modular Crypt Format.
* @return Algorithm name.
*/
public String getName() {
return name;
}

/**
* Get algorithm ID in Bouncy Castle library.
* @return Algorithm ID.
* Hash algorithm constructor for Argon family of algorithms.
* @param name Algorithm name for Modular Crypt Format.
* @param id Algorithm ID in Bouncy Castle library (Argon algorithms only).
*/
public int getId() {
return id;
HashAlgorithm(String name, int id) {
this.name = name;
this.id = id;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;

import java.io.IOException;
Expand Down Expand Up @@ -100,6 +101,10 @@ public CredentialValue protectCredential(String credentialValue, CredentialEntit
final String hashedValue = argon2Hash.toString();
return credentialValueConverter.toDBValue(hashedValue, userId, credentialDefinition);
}
case BCRYPT -> {
String hashedValue = BCrypt.hashpw(credentialValue, BCrypt.gensalt());
return credentialValueConverter.toDBValue(hashedValue, userId, credentialDefinition);
}
default -> throw new InvalidConfigurationException("Unsupported hashing algorithm: " + algorithm);
}
}
Expand Down Expand Up @@ -132,6 +137,13 @@ public boolean verifyCredential(String credentialValue, CredentialEntity credent
}
return succeeded;
}
case BCRYPT -> {
boolean succeeded = BCrypt.checkpw(credentialValue, decryptedCredentialValue);
if (succeeded) {
updateStoredCredentialValueIfRequired(credentialValue, credential);
}
return succeeded;
}
default -> throw new InvalidConfigurationException("Unsupported hashing algorithm: " + algorithm);
}
}
Expand All @@ -154,6 +166,7 @@ public boolean verifyCredentialHistory(String credentialValue, CredentialHistory
final HashAlgorithm algorithm = hashingConfig.getAlgorithm();
return switch (algorithm) {
case ARGON_2I, ARGON_2D, ARGON_2ID -> verifyCredentialUsingArgon2(credentialValue, algorithm, decryptedCredentialValue);
case BCRYPT -> BCrypt.checkpw(credentialValue, decryptedCredentialValue);
};
}

Expand Down Expand Up @@ -335,7 +348,7 @@ private void updateStoredCredentialValueIfRequired(String credentialValue, Crede
updateRequired = true;
} else {
// Check actual argon2 parameters from the hash in the database and compare them with credential definition
updateRequired = updateRequired || !argon2ParamMatch(extractCredentialValue(credential), credentialDefinition.getHashingConfig().getAlgorithm(), credential.getHashingConfig().getParameters());
updateRequired = updateRequired || (credentialDefinition.getHashingConfig().getAlgorithm() != HashAlgorithm.BCRYPT && !argon2ParamMatch(extractCredentialValue(credential), credentialDefinition.getHashingConfig().getAlgorithm(), credential.getHashingConfig().getParameters()));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* PowerAuth Web Flow and related software components
* Copyright (C) 2024 Wultra s.r.o.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.getlime.security.powerauth.app.nextstep.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.getlime.security.powerauth.app.nextstep.NextStepTest;
import io.getlime.security.powerauth.app.nextstep.repository.model.entity.CredentialDefinitionEntity;
import io.getlime.security.powerauth.app.nextstep.repository.model.entity.CredentialEntity;
import io.getlime.security.powerauth.app.nextstep.repository.model.entity.HashConfigEntity;
import io.getlime.security.powerauth.app.nextstep.repository.model.entity.UserIdentityEntity;
import io.getlime.security.powerauth.lib.nextstep.model.entity.CredentialValue;
import io.getlime.security.powerauth.lib.nextstep.model.entity.enumeration.EncryptionAlgorithm;
import io.getlime.security.powerauth.lib.nextstep.model.entity.enumeration.HashAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
* Tests for password hashing.
*
* @author Roman Strobl, [email protected]
*/
class CredentialProtectionServiceTest extends NextStepTest {

@Autowired
private CredentialProtectionService credentialProtectionService;

@Test
void testBcrypt() throws Exception {
final CredentialEntity credentialEntity = new CredentialEntity();
final UserIdentityEntity user = new UserIdentityEntity();
final HashConfigEntity hashConfig = new HashConfigEntity();
final CredentialDefinitionEntity credentialDefinition = new CredentialDefinitionEntity();
credentialDefinition.setEncryptionAlgorithm(EncryptionAlgorithm.NO_ENCRYPTION);
credentialDefinition.setHashingConfig(hashConfig);
hashConfig.setAlgorithm(HashAlgorithm.BCRYPT);
hashConfig.setParameters("{}");
user.setUserId("test");
credentialEntity.setUser(user);
credentialEntity.setCredentialDefinition(credentialDefinition);
credentialEntity.setHashingConfig(hashConfig);
final CredentialValue hashed = credentialProtectionService.protectCredential("test", credentialEntity);
credentialEntity.setValue(hashed.getValue());
assertEquals(EncryptionAlgorithm.NO_ENCRYPTION, hashed.getEncryptionAlgorithm());
assertNotEquals("test", hashed.getValue());
assertTrue(hashed.getValue().startsWith("$2a$10$"));
assertTrue(credentialProtectionService.verifyCredential("test", credentialEntity));
}

@Test
void testArgon2() throws Exception {
final CredentialEntity credentialEntity = new CredentialEntity();
final UserIdentityEntity user = new UserIdentityEntity();
final HashConfigEntity hashConfig = new HashConfigEntity();
final CredentialDefinitionEntity credentialDefinition = new CredentialDefinitionEntity();
credentialDefinition.setEncryptionAlgorithm(EncryptionAlgorithm.NO_ENCRYPTION);
credentialDefinition.setHashingConfig(hashConfig);
hashConfig.setAlgorithm(HashAlgorithm.ARGON_2I);
final Map<String, String> params = Map.of(
"version", "19",
"iterations", "4",
"memory", "16",
"parallelism", "2",
"outputLength", "32"
);
hashConfig.setParameters(new ObjectMapper().writeValueAsString(params));
user.setUserId("test");
credentialEntity.setUser(user);
credentialEntity.setCredentialDefinition(credentialDefinition);
credentialEntity.setHashingConfig(hashConfig);
final CredentialValue hashed = credentialProtectionService.protectCredential("test", credentialEntity);
credentialEntity.setValue(hashed.getValue());
assertEquals(EncryptionAlgorithm.NO_ENCRYPTION, hashed.getEncryptionAlgorithm());
assertNotEquals("test", hashed.getValue());
assertTrue(hashed.getValue().startsWith("$argon2i$v=19$m=65536,t=4,p=2$"));
assertTrue(credentialProtectionService.verifyCredential("test", credentialEntity));
}

}

0 comments on commit 558f13f

Please sign in to comment.