Skip to content

Instantly share code, notes, and snippets.

@shreve
Last active November 1, 2024 22:19
Show Gist options
  • Save shreve/7a6413f087c15b233a69bb46edcfec17 to your computer and use it in GitHub Desktop.
Save shreve/7a6413f087c15b233a69bb46edcfec17 to your computer and use it in GitHub Desktop.
TP-Link Router Config Decrypt

TP-Link Router Config

Update 2021-04-29: This may still work for you if you've got an old TP-Link router, but this is not maintained and doesn't work with newer models. If you've got a newer router, other projects like tpconf_bin_xml will likely work better for you.

TP-Link allows you to backup and restore your router's config file. For some reason, they decided to encrypt these backups so you cannot modify them. I think this is dumb. This script lets you decrypt and re-encrypt your config files so you can modify them as you see fit.

I use this to modify my reserved addresses list because editing them through the web interface is terribly slow and cumbersome.

  1. Go to the router and download the config file from the "Backup & Restore" section of "System Tools".
  2. Run ruby tp.rb config.bin
  3. Edit the created config file config.cfg as you please.
  4. Run ruby tp.rb config.cfg
  5. Return to the "Backup & Restore" section and upload your modified config.bin file.

This work is based on the hack by Matteo Croce here. Thank you for doing the hard part.

#!/usr/bin/env ruby
require 'openssl'
infile = ARGV[0]
content = File.read(infile)
mode = infile.end_with?('.bin') ? :decrypt : :encrypt
# Configure the cipher with what we know about the TP-Link encryption
cipher = OpenSSL::Cipher::DES.new(:ECB)
cipher.key = ["478DA50BF9E3D2CF"].pack("H*")
cipher.padding = 0
case mode
when :decrypt
cipher.decrypt
plaintext = cipher.update(content) + cipher.final
# First 16 bytes are checksum of content
reported_checksum = plaintext[0...16]
plaintext = plaintext[16..-1]
# Trim empty bytes used to pad out the plaintext to be multiple of 8
plaintext = plaintext[0...-1] while plaintext.end_with?("\x0")
checksum = OpenSSL::Digest::MD5.digest(plaintext)
if reported_checksum != checksum
$stderr.puts "Checksums do not match"
$stderr.puts "reported: " << reported_checksum.bytes.map { |b| b.to_s(16) }.join
$stderr.puts "actual: " << checksum.bytes.map { |b| b.to_s(16) }.join
end
File.write(infile.sub('.bin', '.cfg'), plaintext)
when :encrypt
# The body is prefixed with the 16 byte md5 checksum of the contents
checksum = OpenSSL::Digest::MD5.digest(content)
body = checksum + content
# The body needs to be padded out with zero bytes until the byte
# length is a multiple of 8.
remaining = 8 - (body.length % 8)
pad = remaining == 8 ? '' : ("\x0" * remaining)
# Set the cipher to encryption mode and perform the action
cipher.encrypt
ciphertext = cipher.update(body + pad) + cipher.final
File.write(infile.sub('.cfg', '.bin'), ciphertext)
end
@shreve
Copy link
Author

shreve commented Apr 29, 2021

@sengiv thanks. I'll add that and a deprecation notice to this.

@ret5et
Copy link

ret5et commented May 18, 2022

@alpacin0
Copy link

alpacin0 commented Dec 25, 2023

This still works tho. I have a tp-link wifi extender WA860RE and works great. This script will work with tp link products that used DES ecb encryption. Of course I had to use rvm to install an older version of ruby and also openssl 1.0.0 with rvm too, this because OpenSSL 3.0 will give an error on line 11. The DES algorithm was deprecated in newer openSSL. So we have to use openSSL 1.0 that rvm installs by default. Tested it on Ubuntu 22.04 LTS:

  • rvm pkg install openssl
  • rvm remove 2.7.2
  • rvm install 2.7.2 --with-openssl-dir=$HOME/.rvm/usr

If you are willing to try this script do this and you're ready to go.
Thanks a lot Violet for this code! It works like a charm ;) xx

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