Last active
December 23, 2025 14:55
-
-
Save Wh1terat/437a9274c7790aea75a208a944d255b7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| from __future__ import annotations | |
| import secrets | |
| import gzip | |
| import json | |
| import uuid | |
| import requests | |
| from dataclasses import dataclass, asdict, is_dataclass, field | |
| from Crypto.PublicKey import RSA | |
| from Crypto.Cipher import AES, PKCS1_v1_5 | |
| from Crypto.Hash import HMAC, SHA256 | |
| from Crypto.Util.Padding import pad | |
| from base64 import urlsafe_b64encode | |
| from datetime import datetime | |
| class DexcomAPI: | |
| BASE_URL = "https://shareous1.dexcom.com/ShareWebServices/Services" | |
| RSA_PUB = """-----BEGIN PUBLIC KEY----- | |
| MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhR1op97RL74ZPlwmTCz/ | |
| gqJBmezl9XnEd9+qGtBadd9GP8yxTvhxysxS35Hs0vdRty7/uvOiG26Bmy2NAsUw | |
| xaaTy9Jf7Knceg4Zb5HpxcZR7Oku7RBuP9wTqDvLw/DLWIpq/n3norwwfZ5kQtB2 | |
| Q6n/WN6DS6dkJvWozXJS1moBoN66znX3jJDMaq8KSW6xOg1tBPoA7ki3Kgb/NeO8 | |
| xspYhWtjuC7HHxI5O+1elaGgs+Bb5qB2ctKqs909gtcrH62Vo+CdeMVdOHlluaTP | |
| TwudnaVu5zSu0ubcMyca0I4O8IloPJT3buExc2iP4uZtN3lfpjft7PGXAp95QMS4 | |
| 1wIDAQAB | |
| -----END PUBLIC KEY-----""" | |
| def __init__(self, accid, password, devicekey): | |
| self._appid = "d89443d2-327c-4a6f-89e5-496bbb0317db" | |
| self._accid = accid | |
| self._password = password | |
| self._devicekey = devicekey | |
| self._session = requests.session() | |
| self._session.headers.update( | |
| { | |
| "User-Agent": "Dexcom%20Follow/3311 CFNetwork/1406.0.4 Darwin/22.4.0", | |
| "Content-Type": "application/json", | |
| } | |
| ) | |
| @dataclass | |
| class BaseDC: | |
| def __post_init__(self): | |
| try: | |
| self.Timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") | |
| self.RequestId = str(uuid.uuid4()) | |
| except AttributeError: | |
| pass | |
| @dataclass | |
| class EncryptionKey: | |
| KeyId: int | |
| Key: str | |
| IV: str | |
| @dataclass | |
| class SignedRequestHeader(BaseDC): | |
| AccId: str | |
| AppId: str | |
| EncKey: EncryptionKey | |
| IsZip: int | |
| Timestamp: str = field(init=False) | |
| @dataclass | |
| class SignedRequestHeaderUnencrypted(BaseDC): | |
| AccId: str | |
| AppId: str | |
| IsZip: int | |
| Timestamp: str = field(init=False) | |
| @dataclass | |
| class KeyRequest: | |
| KeyRequest: KeyRequestBody | |
| @dataclass | |
| class KeyRequestBody(BaseDC): | |
| AppId: str | |
| Password: str | |
| Key: str | |
| RequestId: str = field(init=False) | |
| Timestamp: str = field(init=False) | |
| KeyHmac: str | |
| def _b64enc(self, data, gz=False): | |
| if isinstance(data, str): | |
| data = data.encode() | |
| if gz: | |
| data = gzip.compress(data) | |
| return urlsafe_b64encode(data).decode("utf-8").replace("=", "") | |
| def _rsa_enc(self, data): | |
| cipher = PKCS1_v1_5.new(RSA.importKey(self.RSA_PUBKEY)) | |
| return self._b64enc(cipher.encrypt(data)) | |
| def _aes_enc(self, data, key, iv): | |
| cipher = AES.new(key, AES.MODE_CBC, iv) | |
| return self._b64enc(cipher.encrypt(pad(data, AES.block_size))) | |
| def _hmac(self, data, secret): | |
| h = HMAC.new(secret.encode(), digestmod=SHA256) | |
| h.update(data.encode()) | |
| return self._b64enc(h.digest()[:16]) | |
| def _uuid(self, uuid1, uuid2): | |
| tmp1 = self._hmac(uuid2, uuid1 + uuid2) | |
| tmp2 = self._hmac(uuid2, uuid1 + tmp1) | |
| return uuid1 + tmp2 | |
| def _tojson(self, data): | |
| if is_dataclass(data): | |
| data = asdict(data) | |
| return json.dumps(data, separators=(",", ":")) | |
| def _jwt(self, obj, data, uuid): | |
| jwt = "{}.{}".format(self._b64enc(self._tojson(obj)), data) | |
| jwt += ".{}".format(self._hmac(jwt, self._uuid(self._accid, uuid))) | |
| return f'"{jwt}"' | |
| def gen_payload(self, data, enc=False): | |
| if not isinstance(data, str): | |
| data = self._tojson(data) | |
| iszip = 1 if len(data) > 500 else 0 | |
| if enc: | |
| aes_key = secrets.token_bytes(16) | |
| aes_iv = secrets.token_bytes(16) | |
| return self._jwt( | |
| self.SignedRequestHeader( | |
| self._accid, | |
| self._appid, | |
| self.EncryptionKey( | |
| 101, | |
| self._rsa_enc(aes_key), | |
| self._rsa_enc(aes_iv) | |
| ), | |
| iszip, | |
| ), | |
| self._aes_enc(self._b64enc(data, iszip).encode(), aes_key, aes_iv), | |
| self._devicekey, | |
| ) | |
| else: | |
| return self._jwt( | |
| self.SignedRequestHeaderUnencrypted( | |
| self._accid, | |
| self._appid, | |
| iszip | |
| ), | |
| self._b64enc(data, iszip), | |
| self._devicekey, | |
| ) | |
| def req_post(self, endpoint, enc=False, data={}): | |
| res = self._session.post( | |
| f"{self.BASE_URL}{endpoint}", self.gen_payload(data, enc) | |
| ) | |
| return res.json() | |
| def devicekey_request(self): | |
| req = self.KeyRequest( | |
| self.KeyRequestBody( | |
| self._appid, | |
| self._password, | |
| self._devicekey, | |
| self._hmac(self._devicekey, self._accid + self._devicekey), | |
| ) | |
| ) | |
| return self.req_post("/Subscriber/DeviceKeys", req, True) | |
| def main(): | |
| # accid & password from CreateSubscriberAccount3 | |
| # devicekey should be random - but consistent once posted to DeviceKeys | |
| dx = DexcomAPI( | |
| accid="CHANGEME", | |
| password="CHANGEME", | |
| devicekey="00000000-0000-0000-0000-00000000000", | |
| ) | |
| # res = dx.devicekey_request() | |
| # print(res) | |
| res = dx.req_post("/Subscriber/ReadSubscriber2") | |
| print(json.dumps(res, indent=4, sort_keys=True)) | |
| if __name__ == "__main__": | |
| main() |
Author
@Dexus In honesty I can't recall, I spent a few days creating this for a friend back in 2023 - once working, I stopped doing anything more.
His goal was to get data as a follower of a Dexcom user rather than the account holder with the end goal of having an RGB lamp in his living room to track a family member's levels.
Given I never had access to the account owners credentials whilst testing, only an invite to follow them - I would assume CreateSubscriberAccount3 relates to creation of the account on Dexcom which the account holder then invites to follow them.
Hope that helps.
Edit:
Looking at some old notes I found, it's the same UUID-like accid and password that are sent to https://uam2.dexcom.com/identity/connect/token 👍
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you share with me where I can catch the "CreateSubscriberAccount3" I don't see it on my captues.