Skip to content

Instantly share code, notes, and snippets.

@mbursa mbursa/kem-decryptor.py

Last active Dec 5, 2019
Embed
What would you like to do?
Python script to decrypt Kamstrup KEM files
# A Python script to decrypt the content of Kamstrup KEM file.
#
# The KEM file is a (sometimes zipped) xml file that contains xml-encrypted data using the xml-enc standard.
# The password needed to decrypt the xml-encrypted data can either be the CustomerId or something else selected by
# the person that created the KEM file.
#
# Usage: python kem-decryptor.py <kem_file> <password>
# kem_file ... the name of the KEM file to decrypt (you need to unzip the the KEM file first, if it is zipped)
# password ... original password used to encrypt the content (16 chars maximum)
#
# The script saves the decrypted content XML content in a file suffixed by '.decrypted.xml' and prints a
# list of meter IDs, serials and AES keys to standard output.
import os
import sys
import base64
import Crypto.Cipher.AES as AES
from xml.dom import minidom
if (len(sys.argv) < 2):
print('ERROR: There are argument missing.')
print('')
print('Usage: %s <kem_file> <password>' % (sys.argv[0]))
sys.exit(1)
#end if
# readn command line arguments: KEM file name and the password
filename = sys.argv[1]
password = sys.argv[2]
# password cannot be longer than 16 characters
if (len(password) > 16):
print('ERROR: Password can have 16 characters maximum.')
print('')
print('Usage: %s <kem_file> <password>' % (sys.argv[0]))
sys.exit(1)
#end if
# read and parse KEM file and extract its encrypted content
# (KEM file is a a XML file formated according http://www.w3.org/2001/04/xmlenc)
xmldoc = minidom.parse(filename)
encrypedtext = xmldoc.getElementsByTagName('CipherValue')[0].firstChild.nodeValue
encrypeddata = base64.b64decode(encrypedtext)
# KEM file data is encrypted with AES-128-CBC cipher and decryption
# requires a 128bit key and 128bit initialization vector. In the case of KEM file,
# the initialization vector is the same as the key. If password length in less
# than 16 characters (128 bits), we pad the key with zeros up to its full length.
key = bytes(str(password).encode("utf-8"))
if (len(key) < 16): key += (16-len(key)) * "\0"
# content decryption
aes = AES.new(key, AES.MODE_CBC, IV=key)
plaintext = aes.decrypt(encrypeddata)
# write decrypted XML into a file
f = open(filename+'.decrypted.xml', 'w')
f.write(plaintext)
f.close()
# print meter number, serial number and AES key for all meters contained
# in the KEM file on standard output
xmldoc = minidom.parseString(plaintext)
for e in xmldoc.getElementsByTagName('Meter'):
meterNo = e.getElementsByTagName('MeterNo')[0].firstChild.nodeValue
serialNo = e.getElementsByTagName('SerialNo')[0].firstChild.nodeValue
key = e.getElementsByTagName('DEK')[0].firstChild.nodeValue
print("%s %s %s" % (meterNo, serialNo, key))
#end for
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.