Background
I'm needing to pull reports from Amazon's Seller Central portal for multiple clients of ours on a weekly basis via a python script. I try to avoid webdrivers due to their inconsistent, error-prone nature across different OSes (from experience). Therefore, I figured it would be a fun little project (and maybe, ultimately, exercise in futility) to try reverse-engineering the login process for sellercentral.amazon.com. The process isn't difficult, save for two fields in the initial login form: password
and metadata1
. The password
field is discussed in detail below. The metadata1
field seems to employ a similar technique, while introducing the additional hurdle of changing every few seconds. When i monitored the metadata1
value it appeared to be generated using a large json object of the various browser metrics. But, one thing at a time, so I'll focus on the password encryption in this post and worry about metadata1
in a future post.
Getting to the point
Upon form submission the value in the password
field is encrypted using techniques discussed further down in this post. The resulting encrypted value then replaces the value in the the password field and renames the field encryptedPwd
My Findings
So far, I've been reviewing the javascript files and stepping through the code sequences and have learned the following:
- They're utilizing a proprietary SiegeCrypto.js script as the main cryptographic library (Siege being Amazon's Secure Ingress and Egress Team) & SubtleCrypto as a part of the process for generating the encryptedPwd - SiegeCrypto is first found in AuthenticationPortalSigninNA.js, declaring the basic initial encryption definition:
SiegeCrypto.addProfile("AuthenticationPortalSigninNA", {
"password": {dataType: "AuthPortalSigninPasswordNA", requiresTail: false},
});
...
SiegeCrypto.addDataType({
"dataTypeId": "AuthPortalSigninPasswordNA",
"jwkPublicKey": {"kty":"RSA","e":"AQAB","n":"gXXZV1VqZ6k_uQtyJNJy5q-qvKdqrXJNgKUO1aYc1UPBVqlhCP0GPxf-0GSo-LEtArgcbF8-j6_vSLSqztYxxF8og--rB8zAyZ8DXZaugX-UiJDQnoJL_HtXKuwIm9U7oEPoeD6H4ZDcfbsPj77xVn7UA2-a90N4aZqMC8EIfXIy1tqSbSPnxPOaiEmy8xGtG-L3RdCyc7TL0Swd_f0_DjRT6ip91IBlCmquoa-xJgZ9e44PVH4AwdyssiV4ZLEZ5yFcE0zcRb_62kx_TQptidbJ4nHocFVjmUW9YsrAWeKrBmOGZEjO4vbATYs1Yf4vgcH7Ix61EPR5sbDP4SlBWQ"},
"providerId": "si:md5",
"keyId": "56d14edce8e2cb6c6842c59ddaee426e"
});
Stepping through the code I was able to find more specifics of the algorithm
*profile* (used by SiegeCrypto)
- password: {dataType: "AuthPortalSigninPasswordNA", requiresTail: false}
*publicKeyProvider* (added as a DataType to SiegeCrypto)
- keyId: 56d14edce8e2cb6c6842c59ddaee426e
- providerId: si:md5
*wrapKey*
- wrappingAlgorithm
- name: RSA-OAEP
- hash: SHA-256
- modulusLength: 2048
- publicExponent: [1, 0, 1]
*Additional Fields*
name: aes_128_gcm_iv12_tag16
encryption: AES-GCM
ivLength: 12
keyLength: 128
tagLength: 128
Later in the process I was able to find the following, which I'm assuming is the parameters amidst being processed according to the encryptions specifications above, but i have no idea how to get to that point
cipherMessage: Uint8Array(413) [1, 128, 0, 20, 124, 132, 165, 153, 149, 96, 94, 4, 210, ...]
messageHeader:
- algorithmId: 20
- contentType: 2
- encryptedDataKeys: [{"keyInfo": "56d14edce8e2cb6c6842c59ddaee426e"}]
- encryptionContext: {}
- frameLength: 12
- headerIvLength: 12
- messageId: Uint8Array(16) [124, 132, 165, 153, 149, 96, ...]
- type: 128
- version: 1
Looking over Amazon's AWS Encryption SDK (Python Repo) I've found the following three pieces that seem to be what I need. However, I'm not sure where to go from here.
# algorithm, mode, data_key_length, iv_length, auth_length, auth_key_length=0
EncryptionSuite.AES_128_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 16, 12, 16)
# algorithm_id, encryption, message_format_version
AlgorithmSuite.AES_128_GCM_IV12_TAG16 = (0x0014, EncryptionSuite.AES_128_GCM_IV12_TAG16, 0x01)
# encryption_type, algorithm, padding_type, padding_algorithm, padding_mgf
WrappingAlgorithm.RSA_OAEP_SHA256_MGF1 = (EncryptionType.ASYMMETRIC, rsa, padding.OAEP, hashes.SHA256, padding.MGF1)
The Question
Can someone provide a short python snippet utilizing the above encryption techniques along with an explanation, so I can see how the encryptedPwd
field value is generated? Here's a dummy password to demonstrate with: Blamazon123
Two example values of the above dummy password as encryptedPwd
(I've added spaces where I saw consistences):
AYAAF P/a2u8yLSNjLWzPRIi0Bac AAAABAAZzaTptZDUAIDU2ZDE0ZWRjZThlMmNiNmM2ODQyYzU5ZGRhZWU0MjZlAQ Brthm+db6k/Oo832X/5U+JtXcBrVnCetjOnvcypG5ZZ6xZr0rXDDMctQevThwGjGYqOOQTy6tFALgMHnjWC2bcBBtyKMhUflpCjGTRodjE7btdqrgExEr07k1ErejaQ1vAW8hQSedfsQR3gyWxJcKKlQ91B4CYO5UMMJzevQyln0SASh5MLW6xOHMnjwdHI8aKFw2ErcvIFg5OpqCDSIyPjifvxkSTue7gJ3fB0ACda04EA5wxmkRteCF753kVGYNBD0h9eOHCPcCm/Y7bWoJAelvqu/U/LxAPkl216deDko4oxjVqLeRy/IExbx6cdEDT7zu0U7HROhvstu8TZE1f AgAAAAAMAAAADAAAAAAAAAAAAAAAA O0Gt/txLoiiXlGQcb5dyFn///// AAAAAQAAAAAAAAAAAAAAAQAAAAv LEJ4zlnbivrzliBrcFGIsPBU3srfmTu91dw4=
AYAAF L1E3ydr57mIKpAQtOrAPsE AAAABAAZzaTptZDUAIDU2ZDE0ZWRjZThlMmNiNmM2ODQyYzU5ZGRhZWU0MjZlAQ AUJX+8tRKZESh1o09BLe6Qj13iuyP5Kb2IC/ipA1mRlWIQtIYApU8792+f5U2x8wv7rTVHcKM8wnFXP2I78PCbo4kXwV5Q6JE99bV4BP+5YnzB1YI6XUgrZ2ubm1wcSV3W1K3OhMogcXIbWjeEjKj2WmpVgSgCXKS6+Z6GxMnE+hArZlNIATYojL7IlLPR5kiGzN4pq86gLzGbfcG2at1MNQ5DdrJtktixLJPU1oFwCtT4AFfy6kiGfoepN+VE0AK0ysMyX3FY7QaI9qLtuA20zQX52NbLzG/qSENYohHzgvOOVzCIr4uwyJ3uXSA0kKXEJ4IbWmQ+k30cotoWRSJW AgAAAAAMAAAADAAAAAAAAAAAAAAAA NUNRLibdfG4P1ac0dL8Ka////// AAAAAQAAAAAAAAAAAAAAAQAAAAv 4vyjW2MLIuuBm8D1c41v5ZwEQFk8k/p4GOss=
Adding Additional Source Code with Analysis
The javascript file largely responsible for generating the metadata1
field can be found here. I've decrypted the functions that I saw were part of the metadata1
generation process. They start on the following lines:
- 1827 (relates to
encryptedPwd
) adds the encryption eventlistener to the form submit action - 332 the core generating function that dispatches the encrypting steps
- 789 & 810 create a crcTable that's used in generating
- 1839 calculates a checksum (used in the
metadata1
process) - 2540 is where the
metadata1
values are returned (from line 332) - 2672 and 2704 are where I noticed the email being converted a hex value, that is prefixed to the
metadata1
contents before it's encrypted
jwkPublicKey
strongly hints at JWT as well. – DisparateencryptedPwd
field is generated. – FreitagencryptedPwd
value right before the signin form is actually submitted. I have been pulling out my hair trying to figure out how to do this, since no matter where I add a "submit" event listener (at the <form>, <body> or document level), that field doesn't seem to exist as an element of the form, and yet, when the POST is actually made, there it is, and thepassword
field is gone. Any guidance on how to intervene after this SiegeCrypto stuff is run? – Vaughnvaught