Skip to content

Instantly share code, notes, and snippets.

@svbnet
Created October 18, 2020 11:22
Show Gist options
  • Save svbnet/915121b77b95620cf768acdc1d2426f9 to your computer and use it in GitHub Desktop.
Save svbnet/915121b77b95620cf768acdc1d2426f9 to your computer and use it in GitHub Desktop.
import hashlib
import os, os.path
import getpass
import getopt
import sys
import base64
import secrets
#
# The file format of the exported config file is basically an OpenSSL-encrypted
# tgz archive with some extra bits of information interleaved in. As it uses
# `openssl enc -a ...` to encrypt the archive, the exported file is multiple
# lines of Base64 data, laid out like so:
#
# [1] Header - concatenated MD5 hex strings of two device-specific parameters,
# appears to be comprised of the device model and some MAC address?
# [2...9] Encrypted data
# [10] File hash - 0-32 is random data, 32-64 is an MD5 hash of the encrypted data
# [11...] Rest of the file
# [end] 64 ='s
#
PASSWORD_BASE = 'pIVWb7ovezIB0/5n5s4oLg=='
def usage():
print('Usage: arcadyan_util [-d(ecrypt) or -e(ncrypt)] [-p factory main WiFi password] path_to_input path_to_output')
print('If -p is omitted, you will be prompted to enter it.')
print()
sys.exit(2)
def decrypt_config(input_path, password, output_path):
try:
os.mkdir(output_path)
except FileExistsError:
pass
with open(input_path, 'rb') as fp:
lines = []
for line in fp.readlines():
if line.startswith(b'===') or len(line) == 0:
break
lines.append(line)
header = lines[0]
header_path = os.path.join(output_path, 'header.bin')
with open(header_path, 'wb') as fp:
fp.write(header)
contents_enc_path = os.path.join(output_path, 'config.tgz.enc')
with open(contents_enc_path, 'wb') as fp:
fp.write(b''.join(lines[1:9]))
fp.write(b''.join(lines[10:]))
contents_path = os.path.join(output_path, 'config.tgz')
os.system(f'openssl enc -aes-256-cbc -salt -d -a -in {contents_enc_path} -out {contents_path} -pass pass:{PASSWORD_BASE}{password}')
print('Decryption complete!')
def encrypt_config(input_path, password, output_path):
header_path = os.path.join(input_path, 'header.bin')
with open(header_path, 'rb') as fp:
header = fp.readline()
contents_path = os.path.join(input_path, 'config.tgz')
contents_enc_path = os.path.join(input_path, 'config.tgz.enc')
os.system(f'openssl enc -aes-256-cbc -salt -a -in {contents_path} -out {contents_enc_path} -pass pass:{PASSWORD_BASE}{password}')
with open(contents_enc_path, 'rb') as fp:
md5 = hashlib.md5()
for chunk in iter(lambda: fp.read(4096), b''):
md5.update(chunk)
contents_hash = md5.hexdigest().encode()
garbage = base64.b64encode(secrets.token_bytes(24))
with open(contents_enc_path, 'rb') as contents_fp:
with open(output_path, 'wb') as out_fp:
line_num = 1
line = contents_fp.readline()
while line:
if line_num == 1:
out_fp.write(header)
line_num += 1
elif line_num == 10:
out_fp.write(garbage + contents_hash + b'\n')
line_num += 1
out_fp.write(line)
line = contents_fp.readline()
line_num += 1
out_fp.write((b'=' * 64) + b'\n')
print('Encryption complete!')
def main():
print('Decryptor/encryptor for Arcadyan/Spark/Skinny VRV9517 router')
try:
opts, args = getopt.getopt(sys.argv[1:], 'dep:')
except getopt.GetoptError as err:
print(err)
usage()
if len(args) < 2:
usage()
for o, a in opts:
if o == '-d':
mode = 'decrypt'
elif o == '-e':
mode = 'encrypt'
elif o == '-p':
password = a
if password is None:
password = getpass.getpass('Enter the factory WiFi password: ')
if password is None:
exit(-1)
password = hashlib.md5(password.encode()).hexdigest()
if mode == 'encrypt':
encrypt_config(args[0], password, args[1])
else:
decrypt_config(args[0], password, args[1])
if __name__ == "__main__":
main()
@johnzoet
Copy link

johnzoet commented May 8, 2022

On my Windows 10 PC command arcadyan_util.py starts Visual Code.
I have to run command like:
python .\arcadyan_util.py -d -p CalmPlattypus55MM SmartModem_backup-2022-05-06.cfg output\

Command output:
python .\arcadyan_util.py -d -p Calm*******55MM SmartModem_backup-2022-05-06.cfg output
Decryptor/encryptor for Arcadyan/Spark/Skinny VRV9517 router
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
bad decrypt
3700:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:../openssl-1.1.1n/crypto/evp/evp_enc.c:610:
Decryption complete!

@surendarkumavat
Copy link

surendarkumavat commented Dec 27, 2024

Where do you get the PASSWORD_BASE value? Salt provider in CH seems to be using similar encryption for locking/encrypting their config files albeit with just the base64 encoding without the header and checksums. I have already verified this by just base 64 decoding the file to get at the encrypted tgz file. but now it gives me bad magic number with the above PASSWORD_BASE value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment