Android library for YouTube cipher deobfuscation and PoToken generation.
Scope: this library deciphers web clients' sig/n and mints poTokens. Which stream clients are
chosen, and how each is tested for whole-song delivery past the CDN's 1-MiB free window, lives in
zemer-app (YTPlayerUtils + tests/client-fulldownload.mjs).
The WebView signature-cipher / n-transform deciphering here (CipherDeobfuscator, CipherWebView,
the injected window._cipherSigFunc) was originally written by me
(alltechdev) - first implemented into zemer-app on
2026-02-12
(f905d49).
This repository is that same code, extracted into a standalone Android library
(com.zemer:cipher).
Remote config: zemer-app fetches player_configs.json
from this repo's master at runtime (via PlayerConfigStore) to self-heal YouTube player rotations
without an app update.
- Signature cipher deobfuscation for YouTube streaming URLs
- N-parameter transformation to avoid throttling
- PoToken generation using BotGuard
- Remote-updatable player configs - a config pushed to this repo's
masterfixes deployed apps within minutes, no APK release needed
YouTube rotates its player_ias JS frequently; each rotation needs a per-player config
(sig call expression, n-transform URL class, signatureTimestamp). All configs live in
one JSON file, which is:
- Bundled in the APK as the offline default,
- Fetched at runtime by
PlayerConfigStorefrom this repo's rawmasterURL (6 h TTL + ETag), and force-refreshed the moment an unknown player breaks deciphering - so pushing a new entry tomasteris the deploy, - Read by the
zemer-apptest harness - app, devices, and tests cannot drift apart.
"445213fb": { "sig": "mP(4,155,INPUT)", "nClass": "Yx", "sts": 20613, "aliases": ["d62bd338"] }- key - the 8-hex player hash from the player JS URL;
aliases- the md5-of-first-10000-bytes fallback hash sig- the signature deobfuscation call, locked toname(int,int,INPUT)nClass- the URL class for the n-transform IIFE (built from a local template)sts- the player's signatureTimestamp
- In
zemer-app:node tests/validate-player-config.mjs <hash>- deciphers a real stream and checks the CDN returns HTTP 206 (the only ground truth; multiple constant pairs can "decipher" while only one is accepted). It prints a paste-ready JSON entry. - Add the entry to
player_configs.jsonhere. Duplicate hashes/aliases reject the whole file - run the unit tests. - Push to
master- deployed apps self-heal from that URL within minutes. - Bump the submodule pointer in
zemer-appafterwards (bundled defaults stay fresh).
The steps above also run automatically. .github/workflows/player-monitor.yml watches for
player_ias rotations, derives each unknown config, validates it against the live CDN, and
(when enabled) commits it straight to master, so a rotation deploys without anyone editing
the file by hand. The deriver and validators live in tools/ (propose-config.mjs,
verify-entry.mjs, apply-entry.mjs).
The pipeline is fail-closed. A hash reaches a commit only after two independent HTTP 206
checks (propose-config, then verify-entry) and a parser-parity gate, and the untrusted
player JS is evaluated only in an isolated job that holds no write credential. After the push
it re-reads the deployed file over git protocol and reverts only a commit it can prove is bad.
Deploy is gated by the AUTO_DEPLOY_CONFIG repository variable, which is also the kill
switch: unset validates and alerts only (writing nothing), branch commits to the branch the
run was triggered from, and master commits to master (the live deploy). The manual steps
above remain the fallback for adding a config by hand.
PlayerConfigParser is the validation boundary: every value is regex-locked so remote data
can never inject free-form JS into the cipher WebView. Invalid entries are skipped; invalid
files (including hash/alias collisions) are rejected wholesale and devices keep their
last-good table. Bump schemaVersion only on breaking shape changes - older apps reject
newer schema files and keep working from their last-good table.
Run the tests with ./gradlew :library:testDebugUnitTest. The config-parity/ fixtures are
shared with the zemer-app harness: file-level accept/reject verdicts (and the n-IIFE
template) are pinned byte-for-byte across both readers.
// Initialize in your Application class
ZemerCipher.initialize(
context = applicationContext,
proxy = yourProxy, // optional
debugLogging = BuildConfig.DEBUG // optional
)// Deobfuscate a signature cipher URL
val deobfuscatedUrl = CipherDeobfuscator.deobfuscateStreamUrl(signatureCipher, videoId)
// Transform n-parameter in URL
val transformedUrl = CipherDeobfuscator.transformNParamInUrl(url)val generator = PoTokenGenerator()
val result = generator.getWebClientPoToken(videoId, sessionId)
// result.playerRequestPoToken - for player requests
// result.streamingDataPoToken - for streaming data requestsAlmost all of this library is original work, written into zemer-app and extracted here
(see Origin above): the WebView signature-cipher and n-transform deciphering, the runtime
execution and script injection, the n-parameter transform logic, and the remote-updatable
config system that lets it self-heal (the player_configs.json schema, PlayerConfigParser,
PlayerConfigStore). The config deriver and live HTTP 206 validator that feed it live in
zemer-app. MetrolistGroup's
faraday ports this config model, deriver, and
validator, and credits zemer-cipher and
zemer-app in its README. MetrolistGroup's
innertubex ships a port of the cipher solver
(its ZemerCipherSolver class) and lists Zemer among its cipher deobfuscation paths.
The automated CI rotation system (watch a player_ias rotation, then derive, live-validate, and
commit the config automatically) was built first by faraday in July 2026. The tools/ pipeline
here is zemer-cipher's own later implementation of that idea (August 2026) on top of this format;
because devices already self-heal from master, a commit here is also the live deploy.
Two narrow pieces build on prior work and are credited here:
- The BotGuard poToken client follows BgUtils (MIT License) patterns.
- Reading YouTube's signature and n functions out of the player is a known deobfuscation technique, also documented by yt-dlp, NewPipe, and others.
GPL-3.0