Repository: privacyidea/privacyidea

Issue:
SHA256 TOTP with Google Authenticator
### Top-level intent
Using SHA256 TOTP with Google Authenticator (and other similar apps)

### Steps to reproduce

1. create a new token
2. select TOTP token with sha256 algorithm
3. scan the QRcode with Google Authenticator
4. the OTP doesn't work

### Expected outcome
privacyIDEA should create a QRCode-String containing an uppercase SHA256

### Actual outcome
We’re testing privacyIDEA (community edition) in our environment.

As reported in the token creation screen, under the SHA drop-down menu, not all authentication apps support sha256 and sha512…
So we’ve tried many authentication apps, and tested the scanning of the QR code created with sha1 (default) and with sha256.
These apps works fine with SHA-1 and SHA-256:
* privacyidea authenticator (open source)
* 2FAS auth (open source)
* freeotp (open source)

These other apps works with SHA1 but don’t work with SHA-256, don’t generating the correct OTP codes with the sha256 algorithm:
* Microsoft authenticator
* Google authenticator

Reading the TOTP string inside the QRcode generated by privacyIDEA, the content is like:
otpauth://totp/TOTP00082AC6?secret=P0T4…FCQ&algorithm=sha256&period=30&digits=6&issuer=test%20aaa%20-%20username
If we try to scan the QR code, apps like Microsoft and Google Authenticator generate the same wrong OTP codes (the same OTP generated with sha1).

The otpauth string contain “sha256” written in lowercase. RFC6238 (received by Google Authenticator) uses uppercase to identify the algorithm: SHA1, SHA256, SHA512 (the same strings are in the Google Authenticator readme ([https://github.com/google/google-authenticator/blob/master/mobile/ios/README 2](https://github.com/google/google-authenticator/blob/master/mobile/ios/README))

Actually, if we try to generate (with a QR encoder) a new QR code containing the string with the algorithm written in capital letters (SHA256), like:
otpauth://totp/TOTP00082AC6?secret=P0T4…FCQ&algorithm=SHA256&period=30&digits=6&issuer=test%20aaa%20-%20username
Google Authenticator works.

### Configuration
* **privacyIDEA version**: Community Edition, 3.9-1jammy
* **Installation method**: Ubuntu package
* **Operating system**: Linux Ubuntu 22.04 LTS
* **Webserver**: Nginx 1.18
* **Token database**: MySQL


Discussion:
Thank you for filing an issue and sharing your observations or ideas. Please be sure to provide as much information as possible to help us to work on this issue.
Interesting find. There is no mention of case-sensitivity in their docs. And for quite some time both authenticator Apps plainly ignored the standard (<https://github.com/google/google-authenticator/wiki/Key-Uri-Format#algorithm>).
We'll look into this.

Resolving patch:
diff --git a/privacyidea/lib/apps.py b/privacyidea/lib/apps.py
--- a/privacyidea/lib/apps.py
+++ b/privacyidea/lib/apps.py
@@ -148,7 +148,7 @@ def create_google_authenticator_url(key=None, user=None,
     url_issuer = quote(issuer.encode("utf-8"))
 
     if hash_algo.lower() != "sha1":
-        hash_algo = "algorithm={0!s}&".format(hash_algo)
+        hash_algo = "algorithm={0!s}&".format(hash_algo.upper())
     else:
         # If the hash_algo is SHA1, we do not add it to the QR code to keep
         # the QR code simpler


Test patch:
diff --git a/tests/test_api_token.py b/tests/test_api_token.py
--- a/tests/test_api_token.py
+++ b/tests/test_api_token.py
@@ -2182,7 +2182,7 @@ def test_25_user_init_defaults(self):
             self.assertTrue(result.get("value"))
             detail = res.json.get("detail")
             googleurl = detail.get("googleurl")
-            self.assertTrue("sha256" in googleurl.get("value"))
+            self.assertTrue("SHA256" in googleurl.get("value"))
             serial = detail.get("serial")
             token = get_tokens(serial=serial)[0]
             self.assertEqual(token.hashlib, "sha256")