Skip to content

Instantly share code, notes, and snippets.

@ArcaneNibble
Last active June 17, 2024 15:20
Show Gist options
  • Save ArcaneNibble/9c4693be4a51c113bc6fefbf55d256ba to your computer and use it in GitHub Desktop.
Save ArcaneNibble/9c4693be4a51c113bc6fefbf55d256ba to your computer and use it in GitHub Desktop.
JLCPCB Color Silkscreen Crypto

JLCPCB Color Silkscreen Crypto

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.

File structure

The structure of an encrypted file is as follows:

  1. 256 bytes RSA-encrypted AES key
  2. 256 bytes RSA-encrypted GCM nonce
  3. Encrypted data
  4. 16 bytes GCM tag

Keys

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-----

Decryption

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

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])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment