apereo/cas · warning
Google Authenticator scratch codes encryption key is not…
Error message
Google Authenticator scratch codes encryption key is not defined. Consider defining the encryption key to securely and safely store scratch codes.
What it means
When scratch-code support is enabled, CAS creates a JasyptNumberCipherExecutor to encrypt scratch codes using the key from cas.authn.mfa.gauth.core.scratch-codes.encryption.key. If that key is blank/undefined, it falls back to a no-op cipher and logs this warning, meaning scratch codes are stored unencrypted. Not a runtime failure, but a security posture warning.
Solutions
- Define cas.authn.mfa.gauth.core.scratch-codes.encryption.key with a Base64-encoded random key (e.g. openssl rand -base64 32)
- Or disable scratch codes entirely if not used, so the no-op branch is irrelevant
- Ensure the key is provided via environment variable/secret store in all deployment environments
Example fix
# before cas.authn.mfa.gauth.core.scratch-codes.encryption.key= # after cas.authn.mfa.gauth.core.scratch-codes.encryption.key=<base64-key>
Defensive patterns
Strategy: validation
Validate before calling
var key = casProperties.getAuthn().getMfa().getGauth().getCore().getScratchCodes().getEncryption().getKey();
if (key == null || key.isBlank()) {
throw new IllegalArgumentException("scratch-codes encryption key must be set");
} Prevention
- Set the scratch-codes encryption key alongside the main gauth crypto keys
- Keep scratch codes disabled unless the feature is actually used
- Template CAS config with required key placeholders that fail fast when empty
When it happens
Trigger: Application context startup with scratch codes condition enabled and cas.authn.mfa.gauth.core.scratch-codes.encryption.key unset or empty.
Common situations: Operators enable Gauth scratch codes but miss the nested scratch-codes encryption key property; keys managed only for the main gauth crypto block but not the scratch-code-specific one; config templates copied from minimal examples.
Understand the failure class
Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Google Authenticator one-time token account…
- Failed to authenticate code
- cannot be found in the registry
- cannot reuse OTP
- Failed to authenticate code
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/25e66409b90e10be.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-gauth/src/main/java/org/apereo/cas/config/GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration.java:164
}
LOGGER.warn("Google Authenticator one-time token account encryption/signing is turned off. "
+ "Consider turning on encryption, signing to securely and safely store one-time token accounts.");
return CipherExecutor.noOp();
}
@ConditionalOnMissingBean(name = "googleAuthenticatorScratchCodesCipherExecutor")
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public CipherExecutor googleAuthenticatorScratchCodesCipherExecutor(final ApplicationContext applicationContext,
final CasConfigurationProperties casProperties) {
return BeanSupplier.of(CipherExecutor.class)
.when(CONDITION_SCRATCH_CODE.given(applicationContext.getEnvironment()))
.supply(() -> {
val key = casProperties.getAuthn().getMfa().getGauth().getCore().getScratchCodes().getEncryption().getKey();
return new JasyptNumberCipherExecutor(key, "googleAuthenticatorScratchCodesCipherExecutor");
})
.otherwise(() -> {
LOGGER.warn("Google Authenticator scratch codes encryption key is not defined. "
+ "Consider defining the encryption key to securely and safely store scratch codes.");
return CipherExecutor.noOp();
})
.get();
}
@ConditionalOnMissingBean(name = "googlePrincipalFactory")
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public PrincipalFactory googlePrincipalFactory() {
return PrincipalFactoryUtils.newPrincipalFactory();
}
}
@Configuration(value = "GoogleAuthenticatorAuthenticationEventExecutionPlanMetadataConfiguration", proxyBeanMethods = false)
@EnableConfigurationProperties(CasConfigurationProperties.class)
static class GoogleAuthenticatorAuthenticationEventExecutionPlanMetadataConfiguration {View on GitHub (pinned to e7288fc434)