Thread Crypt::CBC .ods-Datei entschlüsseln (8 answers)
Opened by payx at 2022-02-12 20:47

payx
 2022-02-17 21:45
#194288 #194288
User since
2006-05-04
564 Artikel
BenutzerIn

user image
Hallo Linuxer und haj,

vielen Dank fürs Mitgrübeln!

Ich bin leider nicht zum Ziel gekommen. Vermutlich fehlt es mir an zu vielen Stellen an tieferem (Crypto-)Verständnis.

Mein Skript sieht jetzt so aus:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/perl

use strict;
use warnings;

######################################################################
# Quellen (-> Kurzbezeichnungen):
# Manifest-Datei innerhalb meiner .ods-Datei als zip geöffnet META-INF\manifest.xml (-> "manifest")
# https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part2-packages/OpenDocument-v1.3-os-part2-packages.html (-> "oasis")
# https://en.wikipedia.org/wiki/OpenDocument_technical_specification#Encryption (-> "wp")
######################################################################

# Crypt::CBC v.3.04 gem. Vorschlag von Linuxer an zwei Stellen angepasst, damit auch 16-Byte-Salts akzeptiert werden:
# Zeile 402: croak "Argument to -salt must be a multiple of 8 bytes long" if defined $salt && (length $salt) % 8 && $salt ne '1';
# Zeile 633: (length $self->{salt}) % 8 and croak "Salt must be a multiple of 8 bytes long";
use Crypt::CBC;
use MIME::Base64;
# Die beiden folgenden sind alternativ (s.u.):
use Crypt::Digest::SHA1 qw( sha1 );
use Crypt::Digest::SHA256 qw( sha256 );
use IO::Uncompress::Inflate qw(inflate $InflateError);
# Nur zu Testzwecken (s. ganz unten):
use IO::Compress::Deflate qw(deflate $DeflateError);
use File::Slurp;

my $xml_encr = read_file('content.xml') or die $!;

# manifest:initialisation-vector="b6PjQ7EG+uWyhk/z29dJJQ=="
my $iv = "b6PjQ7EG+uWyhk/z29dJJQ==";

# oasis Kap. 4.16.5: "The initialization vector is a base64Binary encoded sequence."
$iv = decode_base64($iv);

print "IV: $iv\n";

# manifest:salt="tHaDznWSd1OYCyWzFGx1YA=="
my $salt = "tHaDznWSd1OYCyWzFGx1YA==";

# oasis Kap. 4.16.12 "The salt is encoded in the attribute value as a base64Binary value."
$salt = decode_base64($salt);

print "Salt: $salt\n";

# Mit diesem Passwort und LibreOffice Calc v.7.1.8.1 habe ich die Datei verschlüsselt:
my $pwd = 'pwd123';

# oasis Kap. 3.4.2 Abs. 1: "The start key is generated: The byte sequence representing the password in UTF-8 is used
#                          to generate a 20-byte SHA1 digest (see [RFC3174])."
#$pwd = sha1($pwd);

# wp: ODF versions 1.0 and 1.1 only mandate support for the SHA-1 digest here, while version 1.2 recommends SHA-256.
# manifest:start-key-generation-name="http://www.w3.org/2000/09/xmldsig#sha256"
$pwd = sha256($pwd);

print "PWD: $pwd\n\n";

my $cipher = Crypt::CBC->new(
    -pass => $pwd
    , -iv => $iv
    , -salt => $salt
# wp: "ODF 1.2 ... allows ... AES (with 128, 196 or 256 bits), ... in cipher block chaining mode"
# manifest:algorithm-name="http://www.w3.org/2001/04/xmlenc#aes256-cbc"
    , -cipher => 'Cipher::AES'
    , -chain_mode => 'cbc'
# manifest:key-size="32"
    , -keysize => 32
# manifest:key-derivation-name="PBKDF2"
    , -pbkdf => 'pbkdf2'
# oasis Kap. 3.4.2 Abs. 2: "For each file, a 16-byte salt is generated by a random generator. The salt is used together
#                          with the start key to derive a unique 128-bit key for each file. The default iteration count
#                          for the algorithm is 1024."
    #, -iter => 1024
# manifest:iteration-count="100000"
    , -iter => 100000
    , -header => 'none'
# ISO10126Padding sollte lt. einer Quelle verwendet werden, das kann hier aber nicht eingestellt werden.
    , -padding => 'none'
    #, -hasher => 
);

my $xml_decr = $cipher->decrypt($xml_encr);
print $xml_decr;

print "\n\n----------------------------------------------------------------------\n\n";

# oasis Kap. 3.4.1 "Each file entry that is encrypted shall be compressed with the “deflate” algorithm before being encrypted."
my $xml_decr_inflated;
inflate \$xml_decr => \$xml_decr_inflated or die "inflate failed: $InflateError\n";

print $xml_decr_inflated;

__END__

# Beispiel:

print "\n\n----------------------------------------------------------------------\n\n";

#my $txt = read_file('manifest.xml'); # hier nur als Beispieltext
my $txt = "Foo-nder-Bar";

my ($txt_deflated, $txt_inflated);

deflate \$txt => \$txt_deflated or die "deflate failed: $InflateError\n";

print $txt_deflated;

print "\n\n----------------------------------------------------------------------\n\n";

my $txt_deflated_encr = $cipher->encrypt($txt_deflated);

print $txt_deflated_encr;

print "\n\n----------------------------------------------------------------------\n\n";

my $txt_deflated_decr = $cipher->decrypt($txt_deflated_encr);

print $txt_deflated_decr;

print "\n\n----------------------------------------------------------------------\n\n";

inflate \$txt_deflated_decr => \$txt_inflated or die "inflate failed: $InflateError\n";

print $txt_inflated

Zu den verwendeten Dateien (content.xml, manifest.xml und die ods-Datei) siehe oben (mein erstes Posting).

Der Teil unterhalb von Zeile 92 (bei Bedarf auskommentieren) ist ein Beispiel, in dem Text mit den aktuellen Einstellungen deflatet, encryptet und dann wieder decryptet und inflatet wird. Hier sieht man, dass es prinzipiell funktioniert, und zwar übrigens mit allen Einstellungen, die ich bisher ausprobiert habe (wenn sie nicht direkt auf Fehler liefen).

Nun fällt es mir schwer, auf eine systematische Vorgehensweise bei der Weiterarbeit zu kommen, denn es sind viele Parameter, an denen man schrauben kann. Wie die einzelnen Parameter sich (in ihrem Zusammenspiel) auswirken, kann man nicht feststellen, denn solange nicht alles stimmt, kommt natürlich immer Datenmüll heraus. Ich habe also keine Ahnung, wie nah ich der richtigen Lösung bin.

- Ich weiß nicht, ob die beiden Änderungen in Crypt::CBC nicht zu Fehlfunktionen führen. (Es wird schon irgendeinen Grund für die Festlegung auf 8 Byte gegeben haben.) Letztlich weiß ich nicht, ob man mit Crypt::CBC überhaupt zum Ziel kommen kann.

- Ich habe nicht genau verstanden, welche der Schritte im encryption process, bzw. im rückwärts verlaufenden Entschlüsselungsprozess explizit ausgeführt werden müssen und welche durch Crypt::CBC ausgeführt werden. Die Schlüsselerzeugung via pbkdf2 sollte z.B. inkludiert sein, wenn ich es richtig deute. (Aber wo legt man fest, ob dabei HMAC-SHA1 oder HMAC-SHA256 angewandt wird?)

- Bei manchen Parametern der Crypt::CBC weiß ich nicht, ob ich die Einträge aus dem manifest richtig interpretiert habe, und in manchen Fällen habe ich überhaupt keinen Hinweis auf die richtige Einstellung gefunden.

- Diese beiden fertigen Non-Perl-Lösungen habe ich gefunden; sie behandeln aber ältere ODF-Versionen und haben mir leider für meine Perl-Lösung auch nicht weitergeholfen:
https://selliott.org/encryption/oodecr
https://ringlord.com/odfdecrypt.html

- Und hierfür fehlen mir leider Zeit, Muße und Verständnis:
https://github.com/LibreOffice/core/tree/master/oo...

Schade. Ich lasse das jetzt so stehen und liegen – vielleicht greift es ja mal jemand auf.

Viele Grüße
payx

View full thread Crypt::CBC .ods-Datei entschlüsseln