tiksignV1 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ import base64, hashlib
2
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
3
+ from . import SM3, Simon
4
+ from .CryptoHelpers import *
5
+ from .UrlEncode import parseQueryParams
6
+ from .ProtoBuf import Field, serialize
7
+ SIGN_KEY=bytes.fromhex('ac1adaae95a7af94a5114ab3b3a97dd80050aa0a39314c40528caec95256c28c')
8
+ SM3_OUTPUT=bytes.fromhex('fc78e0a9657a0c748ce51559903ccf03510e51d3cff232d71343e88a321c5304')
9
+ MASK=0xffffffffffffffff
10
+ def bodyHash(stub):
11
+ b=bytes(16) if not stub else hexDecodeString(stub); return SM3.hash(b)[:6]
12
+ def queryHash(query): return SM3.hash(query if query else bytes(16))[:6]
13
+ def _pad(b): n=16-len(b)%16; return b+bytes([n])*n
14
+ def _enc_pb(data):
15
+ pb=_pad(data); keys=[le64(SM3_OUTPUT,i*8) for i in range(4)]; enc=bytearray()
16
+ for i in range(0,len(pb),16):
17
+ a,b=Simon.encrypt((le64(pb,i),le64(pb,i+8)),keys); out=bytearray(16); writeUint64LE(out,0,a); writeUint64LE(out,1,b); enc+=out
18
+ buf=bytearray(bytes.fromhex('f2f7fcfff2f7fcff')+enc); x=bytes(buf); xor=x[:8]; x=bytearray(x)
19
+ for i in range(8,len(x)): x[i]^=xor[i%8]
20
+ x.reverse(); payload=bytes.fromhex('a66ead9f7701d00c18')+bytes(x)+b'ao'
21
+ key=hashlib.md5(SIGN_KEY[:16]).digest(); iv=hashlib.md5(SIGN_KEY[16:]).digest()
22
+ ct=Cipher(algorithms.AES(key),modes.CBC(iv)).encryptor().update(_pad(payload))
23
+ return base64.b64encode(b'\xf2\x81'+ct).decode()
24
+ def getSign(queryhash,stub,timestamp,aid,licenseID,platform,secDeviceID,sdkVersion,sdkVersionInt,randVal=None):
25
+ q=parseQueryParams(queryhash); deviceID=q.get('device_id',''); versionName=q.get('version_name',''); randInt=cryptoRandUint31() if randVal is None else randVal
26
+ b=[Field.varint_field(1,0x20200929<<1),Field.varint_field(2,2),Field.varint_field(3,randInt&0x7fffffff),Field.string(4,str(aid).encode()),Field.string(5,deviceID.encode()),Field.string(6,str(licenseID).encode()),Field.string(7,versionName.encode()),Field.string(8,sdkVersion.encode()),Field.varint_field(9,sdkVersionInt),Field.string(10,bytes(8)),Field.varint_field(11,platform),Field.varint_field(12,timestamp<<1),Field.string(13,bodyHash(stub)),Field.string(14,queryHash(queryhash))]
27
+ b.append(Field.string(15,serialize([Field.varint_field(1,1),Field.varint_field(2,1),Field.varint_field(3,1),Field.varint_field(7,3348294860)])))
28
+ b += [Field.string(16,secDeviceID.encode()),Field.string(20,b'none'),Field.varint_field(21,738)]
29
+ b.append(Field.string(23,serialize([Field.string(1,b'NX551J'),Field.varint_field(2,8196),Field.varint_field(4,2162219008)])))
30
+ b.append(Field.varint_field(25,2)); return _enc_pb(serialize(b))
@@ -0,0 +1,14 @@
1
+ import hashlib, secrets, struct
2
+
3
+ def md5hex(value):
4
+ return hashlib.md5(value.encode() if isinstance(value, str) else value).hexdigest()
5
+ def md5hexFromBytes(value): return hashlib.md5(value).hexdigest()
6
+ def md5Digest(value): return hashlib.md5(value.encode() if isinstance(value, str) else value).digest()
7
+ def bytesToHex(value): return bytes(value).hex()
8
+ def hexByte(value): return f'{value & 255:02x}'
9
+ def firstDigestBytes(digest, n): return bytes.fromhex(digest[:2*n])
10
+ def hexDecodeString(value): return bytes.fromhex(value)
11
+ def cryptoRandByte(): return secrets.randbelow(256)
12
+ def cryptoRandUint31(): return secrets.randbits(31)
13
+ def le64(b, off=0): return struct.unpack_from('<Q', b, off)[0]
14
+ def writeUint64LE(dst, index, value): struct.pack_into('<Q', dst, index*8, value & 0xffffffffffffffff)
@@ -0,0 +1,22 @@
1
+ from .CryptoHelpers import md5hex, firstDigestBytes, hexByte
2
+ def _nibble_swap(b): return ((b>>4)&15)|((b&15)<<4)
3
+ def _bit_reverse(b):
4
+ b=((b&0xf0)>>4)|((b&0x0f)<<4); b=((b&0xcc)>>2)|((b&0x33)<<2); return ((b&0xaa)>>1)|((b&0x55)<<1)
5
+ def _hex_unix(v): return f'{v & 0xffffffff:08x}'
6
+ def v1(params,payload,cookie,unix,b3,b7):
7
+ g=bytearray();
8
+ for s in (params,payload,cookie): g += firstDigestBytes(md5hex(s),4) if s else b'\0'*4
9
+ g += bytes((1,1,2,4)); g += firstDigestBytes(_hex_unix(unix),4)
10
+ val='8404'+hexByte(b7&0xf0)+hexByte(b3)+ '0000'+''.join(hexByte(x) for x in g)
11
+ return str(unix*1000),str(unix),val
12
+ def v2(params,data,cookies,unix):
13
+ p=bytearray()
14
+ for s in (params,data,cookies): p += firstDigestBytes(md5hex(s),4) if s else b'\0'*4
15
+ p += bytes((0,6,11,28)) + int(unix&0xffffffff).to_bytes(4,'big')
16
+ key=bytes.fromhex('df77b940b99b8483d1b9cbd1f7c2b985c3d0fbc3')
17
+ e=bytearray(a^b for a,b in zip(p,key))
18
+ for i in range(20):
19
+ f=_bit_reverse(_nibble_swap(e[i])^e[(i+1)%20]); e[i]=(f^0xff^20)&255
20
+ return str(unix*1000),str(unix),'840280416000'+e.hex()
21
+ def v3(params,data,cookies,unix):
22
+ a,b,c=v2(params,data,cookies,unix); return a,b,'0404b0d30000'+c[len('840280416000'):]
@@ -0,0 +1,22 @@
1
+ import base64
2
+ from .CryptoHelpers import *
3
+ MASK=0xffffffffffffffff
4
+ def ror64(v,n): return ((v>>n)|(v<<(64-n)))&MASK
5
+ def paddingSize(size): return size if size%16==0 else size+(16-size%16)
6
+ def _pad(data,size):
7
+ n=16-size%16; return data[:size]+bytes([n])*n
8
+ def _encrypt_input(table,block):
9
+ d0,d1=le64(block),le64(block,8)
10
+ for i in range(0x22):
11
+ h=le64(table,i*8); d1=(h^(d0+ror64(d1,8)))&MASK; d0=(d1^ror64(d0,0x3d))&MASK
12
+ out=bytearray(16); writeUint64LE(out,0,d0); writeUint64LE(out,1,d1); return bytes(out)
13
+ def encryptLadon(md5hex_bytes,data,size):
14
+ table=bytearray(288); table[:32]=md5hex_bytes
15
+ temp=[le64(table,i*8) for i in range(4)]; b0,b8=temp[0],temp[1]; queue=[temp[2],temp[3]]+[0]*0x22
16
+ for i in range(0x22):
17
+ x9,x8=b0,b8; x8=(ror64(x8,8)+x9)&MASK; x8 ^= i; queue[2+i]=x8; x8 ^= ror64(x9,61); writeUint64LE(table,i+1,x8); b0,b8=x8,queue[i]
18
+ inp=_pad(data,size); return b''.join(_encrypt_input(table,inp[i:i+16]) for i in range(0,len(inp),16))
19
+ def encrypt(khronos,licenseID,aid,randomBytes=None):
20
+ randomBytes=bytes(randomBytes) if randomBytes is not None else secrets.token_bytes(4)
21
+ data=f'{khronos}-{licenseID}-{aid}'.encode(); keygen=randomBytes[:4]+str(aid).encode(); mh=md5hexFromBytes(keygen).encode()
22
+ return base64.b64encode(randomBytes[:4]+encryptLadon(mh,data,len(data))).decode()
@@ -0,0 +1,7 @@
1
+ import json
2
+ def parse(s): return json.loads(s)
3
+ def asObject(o): return o
4
+ def asArray(o): return o
5
+ def optString(m,key): return None if m.get(key) is None else str(m[key])
6
+ def optInt(m,key): return None if m.get(key) is None else int(m[key])
7
+ def optLong(m,key): return None if m.get(key) is None else int(m[key])
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: tiksignV1
3
+ Version: 0.1.0
4
+ Summary: Python port of the TikTok signature algorithms from the referenced Java project
5
+ Author: S1 / JokerPython3 Java-port contributors
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Topic :: Security :: Cryptography
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: cryptography>=41
16
+
17
+ # TikTokSignatrueForJava — Python port
18
+
19
+ This directory is a Python conversion of the Java files in `src/main/java/tiktoksignature` from the referenced repository. It preserves the six-result signing API and the deterministic override fields used by the original test vectors.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ python -m pip install -r requirements.txt
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from tiktoksignature import Param, SignRequest, SignOptions, sign
31
+
32
+ request = SignRequest(
33
+ params=[Param("device_id", "7442000000000000001"), Param("version_name", "41.9.3")],
34
+ data=[Param("type", "3736")],
35
+ )
36
+ options = SignOptions(version=8404)
37
+ signatures = sign(request, options)
38
+ print(signatures.as_headers)
39
+ ```
40
+
41
+ `SignRequest` accepts ordered `Param` sequences for `params`, `data`, `payload`, and `cookie`. `rawBody` can be used to supply a body string verbatim. For reproducible tests, set `timestamp`, `gorgonByte3`, `gorgonByte7`, `argusRand`, and `ladonRandom` in `SignOptions`.
42
+
43
+ ## Validation
44
+
45
+ The included `test_vectors.py` compares all six outputs against the nine golden vectors from the Java repository:
46
+
47
+ ```bash
48
+ PYTHONPATH=. python -m tiktoksignature.test_vectors /path/to/TikTokSignatrueForJava/testdata
49
+ ```
50
+
51
+ All nine vectors pass in this conversion.
52
+
53
+ ## Modules
54
+
55
+ The Java files were converted to corresponding Python modules: `Argus.py`, `CryptoHelpers.py`, `Gorgon.py`, `Ladon.py`, `MiniJson.py`, `Param.py`, `ProtoBuf.py`, `SM3.py`, `SignOptions.py`, `SignRequest.py`, `Signatures.py`, `Signer.py`, `Simon.py`, and `UrlEncode.py`.
56
+
57
+ The implementation is intended for authorized testing and interoperability only. Keep generated signatures, cookies, and device identifiers private.
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+ @dataclass(frozen=True)
4
+ class Param:
5
+ key: str
6
+ value: str
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+ KIND_VARINT, KIND_STRING = 0, 2
3
+ @dataclass
4
+ class Field:
5
+ idx: int; kind: int; varint: int = 0; data: bytes = None
6
+ @staticmethod
7
+ def varint_field(idx, value): return Field(idx, KIND_VARINT, value)
8
+ @staticmethod
9
+ def string(idx, data): return Field(idx, KIND_STRING, data=data)
10
+ def writeVarint(vint):
11
+ out=bytearray(); vint &= 0xffffffff
12
+ while vint > 0x80:
13
+ out.append((vint & 0x7f)|0x80); vint >>= 7
14
+ out.append(vint & 0x7f); return bytes(out)
15
+ def serialize(fields):
16
+ out=bytearray()
17
+ for f in fields:
18
+ out += writeVarint((f.idx << 3) | (f.kind & 7))
19
+ if f.kind == KIND_VARINT: out += writeVarint(f.varint)
20
+ elif f.kind == KIND_STRING: out += writeVarint(len(f.data)) + f.data
21
+ return bytes(out)
@@ -0,0 +1,41 @@
1
+ # TikTokSignatrueForJava — Python port
2
+
3
+ This directory is a Python conversion of the Java files in `src/main/java/tiktoksignature` from the referenced repository. It preserves the six-result signing API and the deterministic override fields used by the original test vectors.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ python -m pip install -r requirements.txt
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from tiktoksignature import Param, SignRequest, SignOptions, sign
15
+
16
+ request = SignRequest(
17
+ params=[Param("device_id", "7442000000000000001"), Param("version_name", "41.9.3")],
18
+ data=[Param("type", "3736")],
19
+ )
20
+ options = SignOptions(version=8404)
21
+ signatures = sign(request, options)
22
+ print(signatures.as_headers)
23
+ ```
24
+
25
+ `SignRequest` accepts ordered `Param` sequences for `params`, `data`, `payload`, and `cookie`. `rawBody` can be used to supply a body string verbatim. For reproducible tests, set `timestamp`, `gorgonByte3`, `gorgonByte7`, `argusRand`, and `ladonRandom` in `SignOptions`.
26
+
27
+ ## Validation
28
+
29
+ The included `test_vectors.py` compares all six outputs against the nine golden vectors from the Java repository:
30
+
31
+ ```bash
32
+ PYTHONPATH=. python -m tiktoksignature.test_vectors /path/to/TikTokSignatrueForJava/testdata
33
+ ```
34
+
35
+ All nine vectors pass in this conversion.
36
+
37
+ ## Modules
38
+
39
+ The Java files were converted to corresponding Python modules: `Argus.py`, `CryptoHelpers.py`, `Gorgon.py`, `Ladon.py`, `MiniJson.py`, `Param.py`, `ProtoBuf.py`, `SM3.py`, `SignOptions.py`, `SignRequest.py`, `Signatures.py`, `Signer.py`, `Simon.py`, and `UrlEncode.py`.
40
+
41
+ The implementation is intended for authorized testing and interoperability only. Keep generated signatures, cookies, and device identifiers private.
tiksignv1-0.1.0/SM3.py ADDED
@@ -0,0 +1,25 @@
1
+ import struct
2
+ IV=[0x7380166F,0x4914B2B9,0x172442D7,0xDA8A0600,0xA96F30BC,0x163138AA,0xE38DEE4D,0xB0FB0E4E]
3
+ TJ=[0x79CC4519]*16+[0x7A879D8A]*48
4
+ MASK=0xffffffff
5
+ def rotl(x,n): return ((x << (n%32)) | (x >> (32-n%32))) & MASK if n%32 else x & MASK
6
+ def p0(x): return x ^ rotl(x,9) ^ rotl(x,17)
7
+ def p1(x): return x ^ rotl(x,15) ^ rotl(x,23)
8
+ def hash(msg):
9
+ if isinstance(msg,str): msg=msg.encode()
10
+ m=bytearray(msg); m.append(0x80)
11
+ while len(m)%64 != 56: m.append(0)
12
+ m += struct.pack('>Q',len(msg)*8); v=IV[:]
13
+ for off in range(0,len(m),64):
14
+ w=list(struct.unpack('>16I',m[off:off+64]))
15
+ for j in range(16,68): w.append((p1(w[j-16]^w[j-9]^rotl(w[j-3],15))^rotl(w[j-13],7)^w[j-6])&MASK)
16
+ w1=[w[j]^w[j+4] for j in range(64)]
17
+ a,b,c,d,e,f,g,h=v
18
+ for j in range(64):
19
+ ff=(a^b^c) if j<16 else ((a&b)|(a&c)|(b&c))
20
+ gg=(e^f^g) if j<16 else ((e&f)|((~e)&g))
21
+ ss1=rotl((rotl(a,12)+e+rotl(TJ[j],j))&MASK,7); ss2=ss1^rotl(a,12)
22
+ tt1=(ff+d+ss2+w1[j])&MASK; tt2=(gg+h+ss1+w[j])&MASK
23
+ d,c,b,a=c,rotl(b,9),a,tt1; h,g,f,e=g,rotl(f,19),e,p0(tt2)&MASK
24
+ v=[(x^y)&MASK for x,y in zip(v,(a,b,c,d,e,f,g,h))]
25
+ return struct.pack('>8I',*v)
@@ -0,0 +1,17 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+ @dataclass
5
+ class SignOptions:
6
+ timestamp: Optional[int] = None
7
+ aid: int = 0
8
+ licenseID: int = 0
9
+ platform: int = 0
10
+ sdkVersion: Optional[str] = None
11
+ sdkVersionInt: int = 0
12
+ version: int = 0
13
+ secDeviceID: Optional[str] = None
14
+ gorgonByte3: Optional[int] = None
15
+ gorgonByte7: Optional[int] = None
16
+ argusRand: Optional[int] = None
17
+ ladonRandom: Optional[bytes] = None
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Sequence
3
+ from .Param import Param
4
+
5
+ @dataclass
6
+ class SignRequest:
7
+ url: Optional[str] = None
8
+ params: Optional[Sequence[Param]] = None
9
+ data: Optional[Sequence[Param]] = None
10
+ payload: Optional[Sequence[Param]] = None
11
+ cookie: Optional[Sequence[Param]] = None
12
+ rawBody: Optional[str] = None
@@ -0,0 +1,19 @@
1
+ from dataclasses import dataclass
2
+
3
+ @dataclass(frozen=True)
4
+ class Signatures:
5
+ xArgus: str
6
+ xLadon: str
7
+ xGorgon: str
8
+ xKhronos: str
9
+ xTTRequestTicket: str
10
+ xSSStub: str
11
+
12
+ @property
13
+ def as_headers(self):
14
+ return {
15
+ 'X-Argus': self.xArgus, 'X-Ladon': self.xLadon,
16
+ 'X-Gorgon': self.xGorgon, 'X-Khronos': self.xKhronos,
17
+ 'X-TT-Request-Ticket': self.xTTRequestTicket,
18
+ 'X-SS-Stub': self.xSSStub,
19
+ }
@@ -0,0 +1,32 @@
1
+ import secrets, time
2
+ from .Param import Param
3
+ from .Signatures import Signatures
4
+ from . import UrlEncode, Gorgon, Argus, Ladon
5
+
6
+ def paramsFromURL(url):
7
+ query=url.split('?',1)[1] if '?' in url else ''; out=[]; seen={}
8
+ for p in query.split('&') if query else []:
9
+ k,v=(p.split('=',1)+[''])[:2] if '=' in p else (p,'')
10
+ if k in seen: out[seen[k]]=Param(k,v)
11
+ else: seen[k]=len(out); out.append(Param(k,v))
12
+ return out
13
+
14
+ def sign(req, opts):
15
+ aid=opts.aid or 1233; licenseID=opts.licenseID or 1611921764; sdkstr=opts.sdkVersion or 'v05.00.06-ov-android'; sdkint=opts.sdkVersionInt or 167775296; unix=opts.timestamp if opts.timestamp is not None else int(time.time())
16
+ payload=list(req.payload or []); data=list(req.data or [])
17
+ if not data and payload: data=payload
18
+ elif not payload and data: payload=data
19
+ params=list(req.params) if req.params is not None else paramsFromURL(req.url) if req.url else []; cookie=list(req.cookie or [])
20
+ payloadStr=UrlEncode.encodeParams(payload); paramsStr=UrlEncode.encodeParams(params); cookieStr=UrlEncode.encodeParams(cookie); dataStr=req.rawBody if req.rawBody is not None and req.rawBody != '' else UrlEncode.encodeParams(data)
21
+ stub=__import__('hashlib').md5(dataStr.encode()).hexdigest(); version=opts.version
22
+ if version==8404:
23
+ b3=opts.gorgonByte3 if opts.gorgonByte3 is not None else secrets.randbelow(256); b7=opts.gorgonByte7 if opts.gorgonByte7 is not None else secrets.randbelow(256); ticket,khronos,gorgon=Gorgon.v1(paramsStr,payloadStr,cookieStr,unix,b3,b7)
24
+ elif version==8402: ticket,khronos,gorgon=Gorgon.v2(paramsStr,payloadStr,cookieStr,unix)
25
+ elif version in (0,4404): ticket,khronos,gorgon=Gorgon.v3(paramsStr,payloadStr,cookieStr,unix)
26
+ else: raise ValueError(f'unsupported gorgon version {version}')
27
+ sec=opts.secDeviceID or ('AadCFwpTyztA5j9L'+''.join('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'[x%62] for x in secrets.token_bytes(9)))
28
+ argus=Argus.getSign(paramsStr,stub,unix,aid,licenseID,opts.platform,sec,sdkstr,sdkint,opts.argusRand)
29
+ ladon=Ladon.encrypt(unix,licenseID,aid,opts.ladonRandom)
30
+ return Signatures(argus,ladon,gorgon,khronos,ticket,stub.upper())
31
+ class Signer:
32
+ sign=staticmethod(sign)
@@ -0,0 +1,13 @@
1
+ MASK=0xffffffffffffffff
2
+ Z=0x3DC94C3A046D678B
3
+ def rol(v,n): return ((v<<n)|(v>>(64-n))) & MASK
4
+ def ror(v,n): return ((v>>n)|(v<<(64-n))) & MASK
5
+ def encrypt(pt,k):
6
+ key=list(k)+[0]*68
7
+ for i in range(4,72):
8
+ tmp=ror(key[i-1],3)^key[i-3]; tmp ^= ror(tmp,1)
9
+ key[i]=((~key[i-4])^tmp^((Z>>((i-4)%62))&1)^3)&MASK
10
+ x,y=pt
11
+ for i in range(72):
12
+ x,y=y,(x^(rol(y,1)&rol(y,8))^rol(y,2)^key[i])&MASK
13
+ return x,y
@@ -0,0 +1,20 @@
1
+ from urllib.parse import quote_plus as _quote_plus
2
+ from .Param import Param
3
+
4
+ def quotePlus(s): return _quote_plus(str(s), safe='-_.~')
5
+ def unquotePlus(s):
6
+ from urllib.parse import unquote_plus
7
+ return unquote_plus(s)
8
+ def encodeParams(pairs):
9
+ return '' if pairs is None else '&'.join(f'{quotePlus(p.key)}={quotePlus(p.value)}' for p in pairs)
10
+ def parseQueryParams(qs):
11
+ out = {}
12
+ if not qs: return out
13
+ for chunk in qs.split('&'):
14
+ for pair in chunk.split(';'):
15
+ if not pair or '=' not in pair: continue
16
+ name, value = pair.split('=', 1)
17
+ if not value: continue
18
+ name, value = unquotePlus(name), unquotePlus(value)
19
+ if name not in out: out[name] = value
20
+ return out
@@ -0,0 +1,6 @@
1
+ from .Param import Param
2
+ from .SignRequest import SignRequest
3
+ from .SignOptions import SignOptions
4
+ from .Signatures import Signatures
5
+ from .Signer import sign, Signer
6
+ __all__=['Param','SignRequest','SignOptions','Signatures','sign','Signer']
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tiksignV1"
7
+ version = "0.1.0"
8
+ description = "Python port of the TikTok signature algorithms from the referenced Java project"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ authors = [{name = "S1 / JokerPython3 Java-port contributors"}]
12
+ license = {text = "MIT"}
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Topic :: Security :: Cryptography",
20
+ ]
21
+ dependencies = ["cryptography>=41"]
22
+
23
+ [tool.setuptools]
24
+ packages = ["tiktoksignature"]
25
+
26
+ [tool.setuptools.package-dir]
27
+ tiktoksignature = "."
28
+
29
+ [tool.setuptools.exclude-package-data]
30
+ "*" = ["testdata/*", "__pycache__/*", "*.pyc"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ import json, pathlib, sys
2
+ from tiktoksignature import Param, SignRequest, SignOptions, sign
3
+ root=pathlib.Path(sys.argv[1] if len(sys.argv)>1 else '../tiktoksig/testdata')
4
+ for path in sorted(root.glob('vector_*.json')):
5
+ d=json.loads(path.read_text()); i=d['input']; pairs=lambda x: None if x is None else [Param(*p) for p in x]
6
+ req=SignRequest(params=pairs(i.get('params')),data=pairs(i.get('data')),payload=pairs(i.get('payload')),cookie=pairs(i.get('cookie')),rawBody=i.get('raw_body'))
7
+ o=SignOptions(timestamp=i.get('timestamp'),aid=i.get('aid',0),licenseID=i.get('license_id',0),platform=i.get('platform',0),sdkVersion=i.get('sdk_version_str'),sdkVersionInt=i.get('sdk_version',0),version=i.get('version') or 0,secDeviceID=i.get('sec_device_id'),gorgonByte3=d.get('random',{}).get('gorgon_byte3'),gorgonByte7=d.get('random',{}).get('gorgon_byte7'),argusRand=d.get('random',{}).get('argus_rand'),ladonRandom=bytes.fromhex(d.get('random',{}).get('ladon_random','')) if d.get('random',{}).get('ladon_random') else None)
8
+ got=sign(req,o); exp=d['expected']; actual={'x_argus':got.xArgus,'x_ladon':got.xLadon,'x_gorgon':got.xGorgon,'x_khronos':got.xKhronos,'x_tt_request_ticket':got.xTTRequestTicket,'x_ss_stub':got.xSSStub}
9
+ assert actual==exp, f'{path.name} mismatch\nexpected={exp}\nactual={actual}'
10
+ print('PASS',path.name)
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: tiksignV1
3
+ Version: 0.1.0
4
+ Summary: Python port of the TikTok signature algorithms from the referenced Java project
5
+ Author: S1 / JokerPython3 Java-port contributors
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Topic :: Security :: Cryptography
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: cryptography>=41
16
+
17
+ # TikTokSignatrueForJava — Python port
18
+
19
+ This directory is a Python conversion of the Java files in `src/main/java/tiktoksignature` from the referenced repository. It preserves the six-result signing API and the deterministic override fields used by the original test vectors.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ python -m pip install -r requirements.txt
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from tiktoksignature import Param, SignRequest, SignOptions, sign
31
+
32
+ request = SignRequest(
33
+ params=[Param("device_id", "7442000000000000001"), Param("version_name", "41.9.3")],
34
+ data=[Param("type", "3736")],
35
+ )
36
+ options = SignOptions(version=8404)
37
+ signatures = sign(request, options)
38
+ print(signatures.as_headers)
39
+ ```
40
+
41
+ `SignRequest` accepts ordered `Param` sequences for `params`, `data`, `payload`, and `cookie`. `rawBody` can be used to supply a body string verbatim. For reproducible tests, set `timestamp`, `gorgonByte3`, `gorgonByte7`, `argusRand`, and `ladonRandom` in `SignOptions`.
42
+
43
+ ## Validation
44
+
45
+ The included `test_vectors.py` compares all six outputs against the nine golden vectors from the Java repository:
46
+
47
+ ```bash
48
+ PYTHONPATH=. python -m tiktoksignature.test_vectors /path/to/TikTokSignatrueForJava/testdata
49
+ ```
50
+
51
+ All nine vectors pass in this conversion.
52
+
53
+ ## Modules
54
+
55
+ The Java files were converted to corresponding Python modules: `Argus.py`, `CryptoHelpers.py`, `Gorgon.py`, `Ladon.py`, `MiniJson.py`, `Param.py`, `ProtoBuf.py`, `SM3.py`, `SignOptions.py`, `SignRequest.py`, `Signatures.py`, `Signer.py`, `Simon.py`, and `UrlEncode.py`.
56
+
57
+ The implementation is intended for authorized testing and interoperability only. Keep generated signatures, cookies, and device identifiers private.
@@ -0,0 +1,39 @@
1
+ Argus.py
2
+ CryptoHelpers.py
3
+ Gorgon.py
4
+ Ladon.py
5
+ MiniJson.py
6
+ Param.py
7
+ ProtoBuf.py
8
+ README.md
9
+ SM3.py
10
+ SignOptions.py
11
+ SignRequest.py
12
+ Signatures.py
13
+ Signer.py
14
+ Simon.py
15
+ UrlEncode.py
16
+ __init__.py
17
+ pyproject.toml
18
+ test_vectors.py
19
+ ./Argus.py
20
+ ./CryptoHelpers.py
21
+ ./Gorgon.py
22
+ ./Ladon.py
23
+ ./MiniJson.py
24
+ ./Param.py
25
+ ./ProtoBuf.py
26
+ ./SM3.py
27
+ ./SignOptions.py
28
+ ./SignRequest.py
29
+ ./Signatures.py
30
+ ./Signer.py
31
+ ./Simon.py
32
+ ./UrlEncode.py
33
+ ./__init__.py
34
+ ./test_vectors.py
35
+ tiksignV1.egg-info/PKG-INFO
36
+ tiksignV1.egg-info/SOURCES.txt
37
+ tiksignV1.egg-info/dependency_links.txt
38
+ tiksignV1.egg-info/requires.txt
39
+ tiksignV1.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ cryptography>=41
@@ -0,0 +1 @@
1
+ tiktoksignature