Reputation: 141
I'm trying to migrate users from an old Drupal 6 CMS to Keycloak. I'd like to migrate the users with their old passwords and then assigning an "Update Password" required action to their profile.
However migrating the passwords seems problematic as I can only access them in their hashed form.
The passwords are hashed with an MD5 algorithm using no salt. I've tried migrating them according to this page: https://lists.jboss.org/pipermail/keycloak-user/2015-December/004212.html
Here's the JSON I'm sending to the Keycloak REST API:
{
"hashedSaltedValue" : "password-hash",
"algorithm" : "restcomm-md5",
"type" : "password",
}
Here's a list of things I've tried
Has anyone ever had any luck getting this feature working?
Upvotes: 8
Views: 9235
Reputation: 118
I'm so late, but my answer may be useful for someone. I have the same problem, we don't want to notify our users to reset password. We are creating users by Keycloak Admin REST API java client. Our user's password are hashed by MD5 algorithm. By default KK don't support MD5, that's why firstly we import custom MD5 password hash provider. Below piece of code that help us.
@Test
public void createUser() {
UserDTO user = UserDTO.builder()
.email("[email protected]")
.username("[email protected]")
.emailVerified(true)
.build();
String rawPassword = "barcelona";
String md5Password = "dea56e47f1c62c30b83b70eb281a6c39";
UserRepresentation userRepresentation = convertToUserRepresentation(user);
//setUserRepresentationPassword(userRepresentation, rawPassword, true);
setUserRepresentationPassword(userRepresentation, md5Password, false);
createUser(userRepresentation);
}
public static UserRepresentation convertToUserRepresentation(UserDTO userDTO) {
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setId(userDTO.getId());
userRepresentation.setEnabled(true);
userRepresentation.setUsername(userDTO.getUsername());
userRepresentation.setFirstName(userDTO.getFirstName());
userRepresentation.setLastName(userDTO.getLastName());
userRepresentation.setEmail(userDTO.getEmail());
userRepresentation.setEmailVerified(userDTO.isEmailVerified());
userRepresentation.singleAttribute("cityId", userDTO.getCityId() != null ? "" + userDTO.getCityId() : null);
userRepresentation.singleAttribute("phone", userDTO.getPhone());
userRepresentation.singleAttribute("phoneVerified", "" + userDTO.isPhoneVerified());
userRepresentation.singleAttribute("notificationsEnabled", "" + userDTO.isNotificationsEnabled());
return userRepresentation;
}
/**
* @return User uuid
*/
public String createUser(UserRepresentation userRepresentation) {
if (CollectionUtils.isEmpty(userRepresentation.getGroups())) {
userRepresentation.setGroups(Arrays.asList(GROUP_USERS));
}
RealmResource realm = keycloak.realm(realmName);
Response response = realm.users().create(userRepresentation);
if (response.getStatus() < 200 || response.getStatus() > 299) {
String error = "User create error: " + response.readEntity(String.class);
log.error(error);
throw new RuntimeException(error);
}
// Extract the uuid of the user we just created.
String location = response.getMetadata().get("Location").get(0).toString();
String uuid = location.substring(location.lastIndexOf("/") + 1);
log.info("User created: " + uuid);
return uuid;
}
/**
* Set password for user
*
* @param userRepresentation user
* @param password raw(plaintext) password or hashed password(this way is deprecated)
* @param isRawPassword password is plaintext
*/
@SneakyThrows
public static void setUserRepresentationPassword(UserRepresentation userRepresentation, String password, boolean isRawPassword) {
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setTemporary(false);
if (isRawPassword) {
credential.setValue(password);
} else {
Field algorithm = credential.getClass().getDeclaredField("algorithm");
algorithm.setAccessible(true);
algorithm.set(credential, "MD5");
Field hashIterations = credential.getClass().getDeclaredField("hashIterations");
hashIterations.setAccessible(true);
hashIterations.set(credential, 0);
Field hashedSaltedValue = credential.getClass().getDeclaredField("hashedSaltedValue");
hashedSaltedValue.setAccessible(true);
hashedSaltedValue.set(credential, password);
}
userRepresentation.setCredentials(Arrays.asList(credential));
}
After that everything is good. I noticed, after I logged in my MD5 password are automatically converted to pbkdf2-sha256.
Upvotes: 2
Reputation: 1318
The parameters hashedSaltedValue etc. are deprecated and keycloak 10 and newer will log a deprecation warning.
There is a new CredentialRepresentation defined where you put JSON into the strings for attributes secretData and credentialData.
Upvotes: 7
Reputation: 196
The following curl command worked for me to migrate a old hashed password. Replace {hashedSaltedValue}
with your hashed password and {salt}
with you salt.
token="..."
curl 'http://keycloak-http/auth/admin/realms/testrealm/users/f:60f0ff50-2cc5-492d-8222-04ac0a9964e1:217b93e8-2830-4392-83e3-9feceea94575' \
-X PUT \
-H "Authorization: $token" \
-H "Content-Type: application/json" \
--data '{"credentials": [ { "algorithm": "pbkdf2-sha512", "hashedSaltedValue": "{hashedpassword}", "hashIterations": 30000, "type": "password", "salt":"{salt}"}]}'
Upvotes: 8
Reputation: 922
Keycloak reset-password api is, what you're trying to use? Using "reset-password" api, I believe it only accepts plain text password, which means, you can't reset-password with already hashed password value.
If you use create user api, then you can add hashed value as password.
I am using Aerobase with Keycloak and try to update password using reset-password api, it's not working with hashed password, it only works with plain text password and then store hashed password instead.
If there's anyone who's successfully reset-password with hashed password, please leave comment here!
Upvotes: 0