Audible icon indicating copy to clipboard operation
Audible copied to clipboard

TEST: New device registration method

Open mkb79 opened this issue 2 years ago • 2 comments

Need help from users for another method to register a new Audible dummy device!

Please tell me if the code below let you register a new device or if you get any error message!

mkb79 avatar Aug 22 '22 13:08 mkb79

import base64
import gzip
import hashlib
import hmac
import json
import secrets
import uuid
from datetime import datetime
from io import BytesIO
from typing import Union

import httpx
from pyaes import AESModeOfOperationCBC, Encrypter, Decrypter


USER_AGENT = "AmazonWebView/GoodreadsForIOS App/4.0.1/iOS/15.4.1/iPhone"


class FrcCookieCipher:

    FRC_SIG_SALT: bytes = b"HmacSHA256"
    FRC_AES_SALT: bytes = b"AES/CBC/PKCS7Padding"
    SIGNATURE_LENGTH: int = 8

    def __init__(self, password: Union[str, bytes]) -> None:
        if isinstance(password, str):
            password = password.encode()

        self.aes_key = self._get_key(password, self.FRC_AES_SALT)
        self.sig_key = self._get_key(password, self.FRC_SIG_SALT)

    def _get_key(self, password, salt: bytes) -> bytes:
        return hashlib.pbkdf2_hmac("sha1", password, salt, 1000, 16)

    def encrypt(self, plaintext: Union[str, bytes, dict]) -> str:
        if isinstance(plaintext, dict):
            plaintext = json.dumps(plaintext)
        if isinstance(plaintext, str):
            plaintext = plaintext.encode()

        compressed = gzip.compress(plaintext)

        iv = secrets.token_bytes(16)
        cipher = Encrypter(AESModeOfOperationCBC(self.aes_key, iv))
        ciphertext = cipher.feed(compressed) + cipher.feed()

        signature = hmac.new(self.sig_key, iv + ciphertext, hashlib.sha256).digest()

        return base64.b64encode(
            b"\0" + signature[:self.SIGNATURE_LENGTH] + iv + ciphertext
        ).decode()

    def decrypt(self, frc: str, verify_signature: bool = True) -> Union[dict, bytes]:
        pad = (4 - len(frc) % 4) * "="
        frc = BytesIO(base64.b64decode(frc + pad))
        frc.seek(1)  # the first byte is always 0, skip them
        signature = frc.read(self.SIGNATURE_LENGTH)
        iv = frc.read(16)
        ciphertext = frc.read()

        if verify_signature:
            assert self._verify_signature(signature, iv, ciphertext) is True, "Signature missmatch"

        decipher = Decrypter(AESModeOfOperationCBC(self.aes_key, iv))
        plaintext = decipher.feed(ciphertext) + decipher.feed()
        decompressed = gzip.decompress(plaintext)

        try:
            return json.loads(decompressed)
        except json.JSONDecodeError:
            return decompressed

    def _verify_signature(self, signature, iv, ciphertext) -> bool:
        new_signature = hmac.new(self.sig_key, iv + ciphertext, hashlib.sha256).digest()
        return signature == new_signature[:self.SIGNATURE_LENGTH]
    

def register(username, password, domain):
    url = f"https://api.amazon.{domain}/auth/register"

    device_serial = secrets.token_hex(16).upper()
    frc = {
        'ApplicationVersion': '3.56.2',
        'DeviceOSVersion': 'iOS/15.5',
        'ScreenWidthPixels': '428',
        'TimeZone': '+01:00',
        'ScreenHeightPixels': '926',
        'ApplicationName': 'Audible',
        'DeviceJailbroken': False,
        'DeviceLanguage': 'de-DE',
        'DeviceFingerprintTimestamp': round(datetime.utcnow().timestamp()) * 1000,
        'ThirdPartyDeviceId': str(uuid.uuid4()).upper(),
        'DeviceName': 'iPhone',
        'Carrier': 'Vodafone.de'
    }
    frc = FrcCookieCipher(device_serial).encrypt(frc)

    headers = {
        "x-amzn-identity-auth-domain": f"api.amazon.{domain}",
        "User-Agent": USER_AGENT,
        "Accept-Encoding": "gzip",
        "Accept": "application/json",
        "Accept-Language": "en-DE",
        "Accept-Charset": "utf-8"
    }

    json_body = {
        "requested_extensions": [
            "device_info",
            "customer_info"
        ],
        "cookies": {
            "website_cookies": [],
            "domain": ".amazon.de"
        },
        "registration_data": {
            "domain": "Device",
            "app_version": "3.56.2",
            "device_serial": device_serial,
            "device_type": "A2CZJZGLK2JJVM",
            "device_name": (
                "%FIRST_NAME%%FIRST_NAME_POSSESSIVE_STRING%%DUPE_"
                "STRATEGY_1ST%Audible for iPhone"
            ),
            "os_version": "15.5.0",
            "software_version": "35602678",
            "device_model": "iPhone",
            "app_name": "Audible"
        },
        "auth_data": {
            "use_global_authentication": "false",
            "user_id_password": {
                "user_id": username,
                "password": password
            }
        },
        "user_context_map": {
            "frc": frc
        },
        "requested_token_type": [
            "bearer",
            "mac_dms",
            "website_cookies"
        ]
    }
    
    r = httpx.post(url, headers=headers, json=json_body)
    return r


if __name__ == "__main__":
    username = ""  # FILL OUT
    password = ""  # FILL OUT
    domain = "de"  # FILL OUT
    current_otp = ""  # FILL OUT IF 2FA is activated
    password += current_otp
    
    r = register(username, password, domain)
    print(r)
    print(r.headers)
    print(r.cookies)
    print(r.json())

mkb79 avatar Aug 22 '22 13:08 mkb79

I've found out myself, how to handle a challenge response from the server (like an OTPCode) when using the code above. But this step needs further user inputs equal to my implemented code. So I think I will don’t switch to this registration method.

mkb79 avatar Sep 05 '22 09:09 mkb79

This issue has not been updated for a while and will be closed soon.

github-actions[bot] avatar Sep 27 '23 03:09 github-actions[bot]

This issue has automatically been closed due to no activities.

github-actions[bot] avatar Oct 08 '23 03:10 github-actions[bot]