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

  1. Define cas.authn.mfa.gauth.core.scratch-codes.encryption.key with a Base64-encoded random key (e.g. openssl rand -base64 32)
  2. Or disable scratch codes entirely if not used, so the no-op branch is irrelevant
  3. 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

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.

Related errors


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)