8000 Increased AES default from 128 to 256 by msdaly200 · Pull Request #50250 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions 8000 docs/documentation/upgrading/topics/changes/changes-26_7_0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ This parameter is sent by clients during token refresh and is used when the `${a
It was primarily used by legacy adapters that have been removed.
This improves security by ensuring that only trusted, pre-registered hosts can be used in backchannel logout URLs.

=== Default AES key size increased to 256 bits

The default key size for newly generated AES key providers (aes-generated) has been increased from 128 bits to 256 bits for improved security.
CNSA 2.0 requires AES-256 for national security systems. AES-128 is not approved for classified data in the post-quantum era.
Existing realms are not affected - only newly created key providers use the new default.
Comment thread
rmartinc marked this conversation as resolved.
Resolved

To migrate an existing realm, add a new aes-generated key provider with key size 32 and a higher priority.
The old provider can be removed once all sessions using the old key have expired.
Comment on lines +191 to +192

// ------------------------ Deprecated features ------------------------ //
== Deprecated features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public class GeneratedAesKeyProviderFactory extends AbstractGeneratedSecretKeyPr

private static final ProviderConfigProperty AES_KEY_SIZE_PROPERTY;

private static final int DEFAULT_AES_KEY_SIZE = 16;
private static final int DEFAULT_AES_KEY_SIZE = 32;

static {
AES_KEY_SIZE_PROPERTY = new ProviderConfigProperty(Attributes.SECRET_SIZE_KEY, "AES Key 8000 size",
"Size in bytes for the generated AES Key. Size 16 is for AES-128, Size 24 for AES-192 and Size 32 for AES-256. WARN: Bigger keys then 128 bits are not allowed on some JDK implementations",
"Size in bytes for the generated AES Key. Size 16 is for AES-128, Size 24 for AES-192 and Size 32 for AES-256.",
LIST_TYPE, String.valueOf(DEFAULT_AES_KEY_SIZE), "16", "24", "32");
}

Expand Down
A5EF
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
Expand Down Expand Up @@ -63,6 +64,8 @@
import org.junit.jupiter.api.Assertions;
import org.openqa.selenium.Cookie;

import static org.keycloak.testsuite.admin.ApiUtil.getCreatedId;

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

/**
Expand Down Expand Up @@ -300,4 +303,93 @@ public void testRestartCookieBackwardsCompatible_Keycloak19() throws IOException
EventAssertion.assertError(events.poll()).type(EventType.LOGIN_ERROR).userId(null).sessionId(null).error(Errors.EXPIRED_CODE)
.details(Details.RESTART_AFTER_TIMEOUT, "true");
}

@Test // #49858
public void testRestartCookieAes128ToAes256Migration() {
// Create cookie with older AES-128 key
RealmResource realm = managedRealm.admin();
String realmId = realm.toRepresentation().getId();
ComponentRepresentation aes128Keys = createComponentRep(
Algorithm.AES,
"aes-generated",
realmId,
new MultivaluedHashMap<>(Map.of(
Attributes.SECRET_SIZE_KEY, List.of("16")
))
);
aes128Keys.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(Long.MAX_VALUE - 10));
try (Response response = realm.components().add(aes128Keys)) {
assertEquals(201, response.getStatus(), "Should create AES-128 key provider");
getCleanup().addComponentId(getCreatedId(response));
}
oauth.openLoginForm();
String aes128Cookie = driver.manage().getCookieNamed(RestartLoginCookie.KC_RESTART).getValue();

// Verify cookie uses AES128 encoding
String aes128Header = aes128Cookie.split("\\.")[0];
String aes128HeaderJson = new String(java.util.Base64.getUrlDecoder().decode(aes128Header), StandardCharsets.UTF_8);
Assertions.assertTrue(aes128HeaderJson.contains("A128GCM"), "Expected pre-migration cookie to use A128GCM (AES-128)");

Comment on lines +328 to +332
// use AES256
ComponentRepresentation aes256Keys = createComponentRep(
Algorithm.AES,
"aes-generated",
realmId,
new MultivaluedHashMap<>(Map.of(
Attributes.SECRET_SIZE_KEY, List.of("32")
))
);
aes256Keys.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(Long.MAX_VALUE - 9));
try (Response response = realm.components().add(aes256Keys)) {
assertEquals(201, response.getStatus(), "Should create AES-256 key provider");
getCleanup().addComponentId(getCreatedId(response));
}

// Verify AES-128 cookie still decrypts with new AES-256 key active
getTestingClient().server(TEST_REALM_NAME).run(session -> {
try {
// Verify the active key is now AES-256
KeyWrapper activeAesKey = session.keys().getActiveKey(
session.getContext().getRealm(),
KeyUse.ENC,
Algorithm.AES
);
Assertions.assertEquals(32, activeAesKey.getSecretKey().getEncoded().length,
"Active AES key should be 256-bit (32 bytes) after migration");

// Verify AES-128 cookie still decrypts
RestartLoginCookie decoded = RestartLoginCookie.decryptAndDecode(session, aes128Cookie);
Assertions.assertNotNull(decoded, "Should decrypt AES-128 cookie with AES-256 key present");
} catch (Exception e) {
Assertions.fail("Failed to decrypt AES-128 cookie after AES-256 migration", e);
}
});

// Generate new cookie and verify it uses AES-256
driver.manage().deleteAllCookies();
oauth.openLoginForm();
String aes256Cookie = driver.manage().getCookieNamed(RestartLoginCookie.KC_RESTART).getValue();

getTestingClient().server(TEST_REALM_NAME).run(session -> {
try {
// Verify new cookie uses A256GCM encryption
String jweHeader = aes256Cookie.split("\\.")[0];
String headerJson = new String(
java.util.Base64.getUrlDecoder().decode(jweHeader),
StandardCharsets.UTF_8
);
Assertions.assertTrue(headerJson.contains("A256GCM"),
"New cookie should use A256GCM encryption after migration");

Comment on lines +375 to +383
// Verify AES256 cookie decrypts correctly
RestartLoginCookie decoded = RestartLoginCookie.decryptAndDecode(session, aes256Cookie);
Assertions.assertNotNull(decoded, "Should decrypt new AES-256 cookie");
} catch (Exception e) {
Assertions.fail("Failed to process AES-256 cookie: ", e);
}
});

assertRestartCookie(aes128Cookie); // AES-128 cookie still works
assertRestartCookie(aes256Cookie); // AES-256 cookie works
}
}
Loading
0