{"record":{"id":"761ee1990e976ac4","repo":"apereo/cas","slug":"google-authenticator-one-time-token-account-encryp","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-gauth/src/main/java/org/apereo/cas/config/GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration.java","lineNumber":147,"sourceCode":"        @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)\n        @Bean\n        @ConditionalOnMissingBean(name = \"googleAuthenticatorInstance\")\n        public CasGoogleAuthenticator googleAuthenticatorInstance(\n            @Qualifier(TenantExtractor.BEAN_NAME)\n            final TenantExtractor tenantExtractor,\n            final CasConfigurationProperties casProperties) {\n            return new DefaultCasGoogleAuthenticator(casProperties, tenantExtractor);\n        }\n\n        @ConditionalOnMissingBean(name = \"googleAuthenticatorAccountCipherExecutor\")\n        @Bean\n        @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)\n        public CipherExecutor googleAuthenticatorAccountCipherExecutor(final CasConfigurationProperties casProperties) {\n            val crypto = casProperties.getAuthn().getMfa().getGauth().getCrypto();\n            if (crypto.isEnabled()) {\n                return CipherExecutorUtils.newStringCipherExecutor(crypto, OneTimeTokenAccountCipherExecutor.class);\n            }\n            LOGGER.warn(\"Google Authenticator one-time token account encryption/signing is turned off. \"\n                + \"Consider turning on encryption, signing to securely and safely store one-time token accounts.\");\n            return CipherExecutor.noOp();\n        }\n\n        @ConditionalOnMissingBean(name = \"googleAuthenticatorScratchCodesCipherExecutor\")\n        @Bean\n        @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)\n        public CipherExecutor googleAuthenticatorScratchCodesCipherExecutor(final ApplicationContext applicationContext,\n                                                                            final CasConfigurationProperties casProperties) {\n            return BeanSupplier.of(CipherExecutor.class)\n                .when(CONDITION_SCRATCH_CODE.given(applicationContext.getEnvironment()))\n                .supply(() -> {\n                    val key = casProperties.getAuthn().getMfa().getGauth().getCore().getScratchCodes().getEncryption().getKey();\n                    return new JasyptNumberCipherExecutor(key, \"googleAuthenticatorScratchCodesCipherExecutor\");\n                })\n                .otherwise(() -> {\n                    LOGGER.warn(\"Google Authenticator scratch codes encryption key is not defined. \"\n                        + \"Consider defining the encryption key to securely and safely store scratch codes.\");","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-gauth/src/main/java/org/apereo/cas/config/GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration.java#L129-L165","documentation":"CAS builds the cipher executor used to encrypt/sign stored Google Authenticator accounts. When cas.authn.mfa.gauth.crypto.enabled is false, it falls back to CipherExecutor.noOp() and logs this warning: one-time token account records (secrets) are stored in cleartext. This is a hardening warning, not a runtime failure.","triggerScenarios":"Bootstrapping the GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration with crypto.enabled=false, typically because no encryption/signing key was configured under cas.authn.mfa.gauth.crypto.","commonSituations":"Fresh CAS deployments where Gauth crypto keys were never generated; environments migrated from older defaults; teams unaware that account secrets land unencrypted in the storage backend (JSON/JDBC/Mongo).","solutions":["Set cas.authn.mfa.gauth.crypto.encryption.key and cas.authn.mfa.gauth.crypto.signing.key (Base64-encoded random keys) and set cas.authn.mfa.gauth.crypto.enabled=true","Generate keys with the CAS-provided key generator or 'openssl rand -base64 32' and store them in a secret manager","Re-encrypt existing stored accounts after enabling crypto if your storage backend supports it"],"exampleFix":"# before\ncas.authn.mfa.gauth.crypto.enabled=false\n# after\ncas.authn.mfa.gauth.crypto.enabled=true\ncas.authn.mfa.gauth.crypto.encryption.key=<base64-32-byte-key>\ncas.authn.mfa.gauth.crypto.signing.key=<base64-32-byte-key>","handlingStrategy":"validation","validationCode":"var crypto = casProperties.getAuthn().getMfa().getGauth().getCrypto();\nif (!crypto.isEnabled()) {\n    LOGGER.warn(\"Gauth account crypto disabled; enable before production\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Include gauth crypto keys in the deployment checklist for production","Generate and store encryption/signing keys in a secret manager, injected via env vars","Audit stored Gauth account records periodically for plaintext secrets"],"tags":["gauth","crypto","security","configuration"],"backgroundTag":"feature-not-enabled","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}