Java -> Python: Arcor/Vodafon DLS Box config decodieren...

Du hast eine Idee für ein Projekt?
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Die EasyBox von Arcor/Vodafon sichert die Konfiguration verschlüßelt ab. Im Internet findet man Java Code um die Datei zu entschlüsseln, z.B. hier: http://www.arcor-user-forum.de/sprachpa ... hode-4550/

Der Ursprüngliche code stammt wohl von http://wiki.openwrt.org/toh/arcadyan/arv7506 und linkt auf http://pastebin.com/AR4t75HR

Der JavaCode ist nicht lang. Doch Java kenne ich so garnicht.

Weiß jemand wie man das in Python übersetzt? Bzw. hat jemand Lust das in Python zu implementieren ?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Der Java-Quelltext selbst sollte relativ einfach zumindest in „unpythonisches” Python übersetzbar sein. So beim kurzen drüber schauen und fast eben so kurzer Recherche im Netz dürfte die AES-Verschlüsselung das Problem sein, oder genauer der Modus CTS. Da habe ich nichts für Python gefunden. Die Bibliotheken können zwar alle 'CBC', 'CFB', 'OFB', und 'ECB', aber 'CTS' scheint eher exotisch zu sein.

Mal so als Anfang die `BytePairDecompressionStream`-Klasse von einer Push-API in eine Pull-API als Generatorfunktion umgeschrieben:

Code: Alles auswählen

def byte_pair_decompress(bytes):
    bytes = iter(bytes)
    escape_value = bytes.next()
    entry_count = ord(bytes.next())
    pair_expansion = dict(
        (bytes.next(), (bytes.next(), bytes.next())
        for _ in xrange(entry_count)
    )
    if bytes.next() != '\x5b':
        raise ValueError('Bad magic after map')
    for value in bytes:
        if value == escape_value:
            yield bytes.next()
        else:
            for value in pair_expansion.get(value, [value]):
                yield value
Die Funktion erwartet ein iterierbares Objekt über Zeichen (also zum Beispiel eine Zeichenkette) und liefert ebenfalls Zeichen.
lunar

@jens: Wofür brauchst Du das in Python? Verwende doch einfach das Java-Programm, wenn es gut funktioniert…

Die Implementierung in Python wird ohnehin schwierig, nicht weil Python per se ungeeignet wäre, sondern weil es keine vernünftige Bibliothek für kryptographische Funktionen gibt.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

In Python selbst konnte ich auch nichts finden. Bin mal die Liste der Implementierungen bei https://en.wikipedia.org/wiki/AES_imple ... ons#Python durchgegangen und nach CTS gesucht und nichts gefunden.
Beschrieben ist RC5-CTS wohl in RFC 2040: https://tools.ietf.org/html/rfc2040

Wie CFB, OFB und CBC funktioniert kann man z.B. bei https://code.google.com/p/slowaes/sourc ... aes.py#450 sehen... Ob CTS komplizierter ist?

Man kann mit Jython arbeiten, z.B.: https://code.google.com/p/8-bits/source ... ecrypto.py

Code: Alles auswählen

from javax.crypto import Cipher

cipher = Cipher.getInstance("AES/CTS/NoPadding")
Allerdings kann ich dann genauso gut direkt das original Java Programm nehmen.

@lunar: Klar kann ich auch die Java Implementierung nehmen. Hab ich auch gemacht. Nur interessehalber möchte ich gern das selbe in Python sehen. Schade das es nicht so einfach ist...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
lunar

@jens: Das wäre dann ein idealer Zeitpunkt, um Java zu lernen, und sich in Kryptographie einzuarbeiten… :)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

lunar hat geschrieben:@jens: Das wäre dann ein idealer Zeitpunkt, um Java zu lernen
Es gibt einen guten Zeitpunkt um Java zu lernen?! :twisted:
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

EyDu hat geschrieben: Es gibt einen guten Zeitpunkt um Java zu lernen?! :twisted:
Das ist so ähnlich wie beim Kinderkriegen... den idealen Zeitpunkt gibt es nie - aber mal im Ernst: Es gibt wirklich schlimmeres als Java! ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@lunar: Warum sind die vorhandenen Bibliotheken nicht vernünftig? Ich habe auf Anhieb vier gefunden die offensichtlich die „normalen” Modi ('CBC', 'CFB', 'OFB', und 'ECB') und AES können. PyCrypto, m2crypto, ncrypt, und pycryptopp. Nur halt CTS nicht.

@jens: Zu CTS gibt es einen englischsprachigen Wikipedia-Eintrag wo erklärt wird, wie sich das von anderen Modi unterscheidet. Sieht so aus, als könnte man auf bestimmten anderen Aufbauen und müsste nur die letzten beiden Blöcke besonders behandeln. Das ist dazu da um bei einem Block-Cipher ohne explizites Padding der Ausgangsdaten aus zu kommen. Dazu werden Daten aus dem vorletzen Block genommen um den letzten aufzufüllen. Falls ich das richtig verstanden habe.

Wenn ich keinen Fehler gemacht habe, und Du eine Crypto-Bibliothek findest, die AES im CTS-Modus kann, dann müsste das ungefähr so aussehen:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from hashlib import md5
from struct import Struct
from Crypto.Cipher import AES


KEYS = [
    '\x1d\xe5\xfa\xb8\x81\x91\x00\x84\x11\xaf\x53\xdd\xe4\x89\xea\xfd',
    '\xa9\xdc\x0e\xf0\x28\x67\x91\xac\xc9\x13\x86\x5d\x2d\xf8\x20\x99',
    '\x1a\xa8\x80\xb5\x44\x11\xf1\x19\x0c\xca\x3d\xe8\x77\x41\x37\x89',
    '\x45\x68\x70\x98\x7a\xcd\xeb\xbb\xbd\xed\xa8\x03\x98\x73\x40\x24',
]
HEADER_STRUCT = Struct('<iib16s')
HEADER_SIZE = 0x28
PAYLOAD_HEADER_STRUCT = Struct('<HBBH')


def byte_pair_decompress(bytes):
    escape_value = bytes.next()
    entry_count = ord(bytes.next())
    pair_expansion = dict(
        (bytes.next(), (bytes.next(), bytes.next()))
        for _ in xrange(entry_count)
    )
    if bytes.next() != '\x5b':
        raise ValueError('Bad magic after map')
    for value in bytes:
        if value == escape_value:
            yield bytes.next()
        else:
            for value in pair_expansion.get(value, [value]):
                yield value


def unpack_header(data, expected_magic, check_key_number=True):
    magic, size, key_number, expected_md5 = HEADER_STRUCT.unpack(
        data[:HEADER_STRUCT.size]
    )
    if magic != expected_magic:
        raise ValueError('Bad magic')
    if check_key_number:
        if not (0 <= key_number < len(KEYS)):
            raise ValueError('Bad key number (0-%d expected)' % len(KEYS) - 1)
    return size, key_number, expected_md5


def extract_config(in_file=sys.stdin, out_file=sys.stdout):
    init_vector = in_file.read(16)
    cipher = AES.new(KEYS[0], AES.MODE_CBC, init_vector) # BUG Mode is incorrect.
    
    header = cipher.decrypt(in_file.read(HEADER_SIZE))
    size, key_number, expected_md5 = unpack_header(header, 0x59585756)
    
    body = in_file.read(size)
    if expected_md5 != md5(body).digest():
        raise ValueError('Body MD5 does not match')
    
    cipher = AES.new(KEYS[key_number], AES.MODE_CBC, body[:16]) # BUG See above.
    payload = cipher.decode(body[16:])
    
    size, _key_number, expected_md5 = unpack_header(payload, 0x48464442, False)
    if expected_md5 != md5(payload[HEADER_SIZE:]).digest():
        raise ValueError('Payload MD5 does not match')
    
    data_offset = HEADER_SIZE + PAYLOAD_HEADER_STRUCT.size
    magic, levels, size_high, size_low = PAYLOAD_HEADER_STRUCT.unpack(
        payload[HEADER_SIZE:data_offset]
    )
    if magic != 0x6562:
        raise ValueError('Byte-pair compression magic does not match')
    compressed_size = size_high << 16 | size_low
    
    bytes = payload[data_offset:compressed_size]
    for _ in xrange(levels):
        bytes = byte_pair_decompress(bytes)
    
    out_file.writelines(bytes)


def main():
    extract_config()


if __name__ == '__main__':
    main()
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

@BlackJack: Wow, danke dafür...

Leider funktioniert das auf anhieb nicht:

Code: Alles auswählen

    header = cipher.decrypt(in_file.read(HEADER_SIZE))
ValueError: Input strings must be a multiple of 16 in length
HEADER_SIZE ist wie im original = 0x28 also == 40 und somit nicht durch 16 teilbar.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Ja natürlich funktioniert das nicht. Das habe ich doch geschrieben, dafür wäre der CTS-Modus da um aus einem Block-Cipher einen zu machen der auch Daten verarbeiten kann die nicht ein Vielfaches der Blockgrösse gross sind. Das sind die beiden Stellen im Quelltext wo der BUG-Kommentar steht.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ah, alles klar und ich hab mich schon gewundert, weil ich nicht erkannt habe, wo der CTS Teil ist ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
lunar

@BlackJack: BouncyCastle ist das Maß aller Dinge, was Kryptographie angeht, und nur m2crypto kann damit annähernd mithalten, aber eben nur annähernd, wie diese Diskussion zeigt. PyCrypto und pycyptopp unterstützen nur Algorithmen, keine Protokolle wie S/MIME oder OpenPGP, ja nicht einmal Padding-Modi für Blockchiffren, oder Funktionen zur Schlüsselerzeugung wie PBKDF2. Von der ziemlich unterirdischen Dokumentation dieser Bibliotheken ganz zu schweigen…

Zu ncrypt habe ich keine näheren Informationen außer der Seite im Cheeseshop finden können.
Antworten