JLCPCB color silkscreen files are encrypted using AES-128-GCM and RSA-2048 with OAEP padding with SHA-256 as the hash function. Unfortunately, this means that, although it is possible for tools other than EasyEDA to create files, only JLCPCB can decrypt the resulting files.
The structure of an encrypted file is as follows:
- 256 bytes RSA-encrypted AES key
- 256 bytes RSA-encrypted GCM nonce
- Encrypted data
- 16 bytes GCM tag
The JLCPCB public keys is as follows:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPtuUqJecaR/wWtctGT8
QuVslmDH3Ut3s8c1Ls4A+M9rwpeLjgDUqfcrSrTHBrl5k/dOeJEWMeNF7STWS5jo
WZE0H60cvf2bhormC9S6CRwq4Lw0ua0YQMo66R/qCtLVa5w6WkaPCz4b0xaHWtej
JH49C0T67rU2DkepXuMPpwNCflMU+WgEQioZEldUTD6gYpu2U5GrW4AE0AQiIo+j
e7tgN8PlBMbMaEfu0LokZyth1ugfuLAgyogWnedAegQmPZzAUe36Sni94AsDlhxm
mjFl+WQZzD3MclbEY6KQB5XL8zCR/J6pCUUwfHantLxY/gQi0XJG5hWWtDyH/fR2
lwIDAQAB
-----END PUBLIC KEY-----
If you were to have the private key (which you don't, but, for testing/demonstration purposes, you can patch the key inside the EasyEDA JavaScript code), you can decrypt files using something like the following:
# Decrypt AES key
$ dd if=Fabrication_ColorfulTopSilkscreen.FCTS bs=1 count=256 skip=0 | openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -inkey privatekey.pem | xxd -ps
256+0 records in
256+0 records out
256 bytes transferred in 0.000624 secs (410256 bytes/sec)
bd0ec4aeb9bc1c039f2678379954bab1 # Key
# Decrypt GCM nonce
$ dd if=Fabrication_ColorfulTopSilkscreen.FCTS bs=1 count=256 skip=256 | openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -inkey privatekey.pem | xxd -ps
256+0 records in
256+0 records out
256 bytes transferred in 0.000511 secs (500978 bytes/sec)
c3adaf283b3cdcbf6ea9b13915bc1de4 # Nonce
# Decrypt payload using PyCryptodome
$ python3
Python 3.12.3 (main, Apr 9 2024, 16:03:47) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from Crypto.Cipher import AES
>>> c = AES.new(bytes.fromhex('bd0ec4aeb9bc1c039f2678379954bab1'), AES.MODE_GCM, bytes.fromhex('c3adaf283b3cdcbf6ea9b13915bc1de4'))
>>> with open('Fabrication_ColorfulTopSilkscreen.FCTS', 'rb') as f:
... data = f.read()
...
>>> dec = c.decrypt_and_verify(data[512:-16], data[-16:])
>>> dec
b'<?xml version="1.0" encoding="UTF-8" standalone="no"?> ...
Encryption using PyCryptodome:
#!/usr/bin/env python3
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from secrets import token_bytes
PUBKEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPtuUqJecaR/wWtctGT8
QuVslmDH3Ut3s8c1Ls4A+M9rwpeLjgDUqfcrSrTHBrl5k/dOeJEWMeNF7STWS5jo
WZE0H60cvf2bhormC9S6CRwq4Lw0ua0YQMo66R/qCtLVa5w6WkaPCz4b0xaHWtej
JH49C0T67rU2DkepXuMPpwNCflMU+WgEQioZEldUTD6gYpu2U5GrW4AE0AQiIo+j
e7tgN8PlBMbMaEfu0LokZyth1ugfuLAgyogWnedAegQmPZzAUe36Sni94AsDlhxm
mjFl+WQZzD3MclbEY6KQB5XL8zCR/J6pCUUwfHantLxY/gQi0XJG5hWWtDyH/fR2
lwIDAQAB
-----END PUBLIC KEY-----"""
def encrypt(inp_fn, out_fn):
with open(inp_fn, 'rb') as f:
inp = f.read()
key = token_bytes(16)
nonce = token_bytes(16)
# AES-GCM
c = AES.new(key, AES.MODE_GCM, nonce)
enc, tag = c.encrypt_and_digest(inp)
# RSA-OAEP
pubkey = RSA.import_key(PUBKEY)
rsa = PKCS1_OAEP.new(pubkey, SHA256)
enc_key = rsa.encrypt(key)
enc_nonce = rsa.encrypt(nonce)
# assemble
output = enc_key + enc_nonce + enc + tag
with open(out_fn, 'wb') as f:
f.write(output)
if __name__=='__main__':
import sys
if len(sys.argv) < 3:
print("Usage: {sys.argv[0]} inp.svg out.FCxx")
sys.exit(-1)
encrypt(sys.argv[1], sys.argv[2])