ubuntuusers.de

Frage zu Encodings in Python (Problem mit Umlauten und Sonderzeichen in Strings)

Status: Gelöst | Ubuntu-Version: Ubuntu 11.10 (Oneiric Ocelot)
Antworten |

droebbel Team-Icon

Anmeldungsdatum:
19. Oktober 2004

Beiträge: 5388

Moin.

Folgendes kleines Problem habe ich gerade: ich benötige in Python einen Generator, der aus einer Liste von Zeichen alle möglichen Kombinationen bildet (muss mein Revelation-Passwort knacken). Das funktionierte herrlich einfach, solange ich zum Testen nur normale Buchstaben verwendete (Beispielcode)

1
2
3
4
5
6
for i in itertools.product('au',repeat=2): print "".join(i)
... 
aa
au
ua
uu

Nun ist im zu testenden Passwort aber dummerweise sowohl ein ö als auch ein %, und da bekomme ich nun folgende aparte Kombinationen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
for i in itertools.product('ö%',repeat=2): print "".join(i)
... 

ö
�%

��
�%
%
%�
%%

Was ich bisher zu Encodungs gelesen habe, brachte mich nicht weiter - was muss ich tun? Python-Version ist 2.7.

Gruß David

Kinch

Anmeldungsdatum:
6. Oktober 2007

Beiträge: 1261

Wenn möglich, wäre die einfachste Möglichkeit den String mit den Zeichen direkt in den Python-Quellcode zu schreiben. Du musst Python2 noch sagen, dass der Quellcode in UTF8 geschrieben ist.

Dazu:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

an den Anfang der Datei schreiben. Eventuell — bin nicht mehr so in der Python UTF8-Problematik drinnen — den String auch so initialisieren:

u"λιϵαχκονρ"

Ansonsten müsstest du die Datei einlesen und die Daten zu utf8 kodieren. Glaube(!), das ging so:

decoded_string = a_string.decode("utf-8")

Möglicherweise auch 'encode'. Kann mir nie merken, wann was genommen wird.

jakon Team-Icon

Lokalisierungsteam

Anmeldungsdatum:
16. November 2009

Beiträge: 419

Interaktiv: (Im Terminal mit UTF-8)

1
2
3
4
5
6
7
8
9
>>> from itertools import product
>>> for c in product("ö%".decode('utf8'), repeat=2):
...     print "".join(c)
... 
öö
ö%

%%
>>> 

Im Skript:

1
2
3
4
5
# encoding: utf-8
from itertools import product

for c in product(u"ö%", repeat=2):
    print "".join(c)

Edit: Die zweite Lösung geht übrigens auch in der python-Shell, aber nicht mit ipython, da kommt sowas raus: „ö“. Deswegen auch der Beitrag hier …

droebbel Team-Icon

(Themenstarter)

Anmeldungsdatum:
19. Oktober 2004

Beiträge: 5388

Ihr seid unglaublich - so schnell hätte ich nicht mal eine einzige Antwort erwartet.

Die Konstruktion der Passwörter klappt nun, es folgt das nächste Problem, das sicher auch mit Codierung zu tun hat, weshalb ich hier mal fortsetze.

Ich glaube, der Konsolencode spricht für sich:

1
2
3
4
5
6
7
8
from Crypto.Cipher import AES
>>> password="oooooooooooooooooooooooooooooooo"
>>> cipher = AES.new(password)
>>> password="oooooooooooooooooooooooooooooooö"
>>> cipher = AES.new(password)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: AES key must be either 16, 24, or 32 bytes long

Denn

1
2
3
4
5
6
>>> password="oooooooooooooooooooooooooooooooo"
>>> print len(password)
32
>>> password="oooooooooooooooooooooooooooooooö"
>>> print len(password)
33

Autsch. Jedes Sonderzeichen belegt also doppelten Platz.

Dann füllt Revelation mit \0 auf 32 Zeichen auf - nur sind das dann dank der Sonderzeichen mehr als 32 bit.

Nun hat Revelation mein Passwort ja irgendwie verarbeitet, und ich frage mich, was unterwegs passiert, damit ich die selbe Behandlung meinen BruteForce-Passwörtern angedeihen lassen kann. Ich habe nach relevantem Code gekramt und folgendes gefunden:

Passworteingabe:

 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
class PasswordOpen(Password):
	"Password dialog for opening files"

	def __init__(self, parent, filename):
		Password.__init__(
			self, parent, _('Enter file password'),
			_('The file \'%s\' is encrypted. Please enter the file password to open it.') % filename,
			gtk.STOCK_OPEN
		)

		self.entry_password = self.add_entry(_('Password'))


	def run(self):
		"Displays the dialog"

		response = Password.run(self)
		password = self.entry_password.get_text()
		self.destroy()

		if response == gtk.RESPONSE_OK:
			return password

		else:
			raise CancelError

Die Klasse Datafile hat folgendes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
	def load(self, file, password = None, pwgetter = None):
		"Loads a file"

		file = file_normpath(file)
		data = file_read(file)

		if self.__handler == None:
			self.__handler = datahandler.detect_handler(data)()

		self.__handler.check(data)

		if self.__handler.encryption == True and password is None and pwgetter != None:
			password = pwgetter()

		entrystore = self.__handler.import_data(data, password)

		self.set_password(password)
		self.set_file(file)

		return entrystore

Und die Klasse Revelation(ui.App) hat

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        def __file_load(self, file, password, datafile = None):
		"Loads data from a data file into an entrystore"

		try:
			if datafile is None:
				datafile = self.datafile

			while 1:
				try:
					return datafile.load(file, password, lambda: dialog.PasswordOpen(self, os.path.basename(file)).run())

Ich kann nicht garantieren, dass das alles ist, und als jemand, der gerade zum ersten Mal Kontakt mit Python hat und beruflich rein gar nichts mit Programmierung zu tun hat (bin Pianist) ist mir schleierhaft, was da unterwegs mit dem eingegebenen Passwort geschieht. Was ich aber tun konnte: ich habe etwas Code in die Funktionen zum Speichern eingefügt. In Revelation haben auch die Passwortstrings mit Sonderzeichen normale Länge. Sind sie evtl noch anders codiert? Und wie krieg ich das für meine Passwörter hin?

P.S.: um genau zu sein, ich habe die Passwörter testweise ausgeben lassen, unmittelbar bevor sie beim Anlegen einer Datei für die Verschlüsselung verwendet werden. Es sind genau die eingegebenen Passwörter, und die Länge passt.

Kinch

Anmeldungsdatum:
6. Oktober 2007

Beiträge: 1261

Nun, in UTF8 sind Zeichen unterschiedlich lang in der Anzahl ihrer Bytes.

Abhängig davon, welche Sonderzeichen genau vorkommen, könntest du zum Beispiel latin-1 als Kodierung wählen. Da ist jedes Zeichen genau ein Byte lang. Wäre an der Stelle natürlich schon wichtig zu wissen, welche Kodierung die Zeichen da ursprünglich hatten.

droebbel Team-Icon

(Themenstarter)

Anmeldungsdatum:
19. Oktober 2004

Beiträge: 5388

Kinch schrieb:

Nun, in UTF8 sind Zeichen unterschiedlich lang in der Anzahl ihrer Bytes.

Abhängig davon, welche Sonderzeichen genau vorkommen, könntest du zum Beispiel latin-1 als Kodierung wählen. Da ist jedes Zeichen genau ein Byte lang. Wäre an der Stelle natürlich schon wichtig zu wissen, welche Kodierung die Zeichen da ursprünglich hatten.

Das versuche ich gerade herauszubekommen, komme aber nicht weiter. Ich habe etwas Testcode in die Routine eingefügt, die beim Anlegen einer Datei den verschlüsselten Container anlegt. Unmittelbar vor Initialisierung der Verschlüsselung wird folgendes ausgeführt:

1
2
3
		print "Passwort nach Padding: ", password, " Länge ", len(password)
		print "decodiert: ", password.decode(sys.getdefaultencoding())
		print "Encoding ", sys.getdefaultencoding()

ergibt

Passwort nach Padding:  oooooooö  Länge  32
decodiert:  oooooooö
Encoding  utf-8

Weiter bringt mich das noch nicht. Wenn der String utf-codiert wäre, dann müsste ja die nachfolgende Verschlüsselung fehlschlagen, weil der 32-Zeichen-String 33 Bytes lang wäre. Oder? Wie um alles in der Welt kann ich herausbekommen, wie ein bestimmter String codiert ist? Nur eine zu nehmen, mit der die Verschlüsselung funktioniert, hilft nicht viel, denn wenn ich nicht die selbe Codierung verwende wie revelation bei der Verschlüsselung, ist das ganze Brute-Force-Unterfangen mit einer begrenzten Zeichenliste ja witzlos.

EDIT:

Die Codierung ist Unicode. Es mag eine krude Methode sein, aber ich habe einfach mal den eingegebenen Passwortstring zerlegen lassen.

1
2
3
		for char in password:
			print "wert von Zeichen ", char, " ist ", ord(char)
		print password

Ergebnis:

wert von Zeichen  l  ist  108
wert von Zeichen  �  ist  195
wert von Zeichen  �  ist  182
wert von Zeichen  �  ist  195
wert von Zeichen  �  ist  164
wert von Zeichen  %  ist  37
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
wert von Zeichen    ist  0
löä%

Die Zeichen ö und ä belegen also jedenfalls zwei Bytes. Zeigt man den string per print an oder verwendet ihn, bekommt man auch ganz richtig die Zeichen ö und ä. Dennoch ist die Länge 6. Zur Codierung: 195 ist hex c3, 182 ist hex b6. Das Unicode-Zeichen c3b6 ist ö. Passt also.

In der Python-Konsole kann ich das reproduzieren:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
>>> pw="löä%"
>>> len(pw)
6
>>> for i in pw:
...     ord(i)
... 
108
195
182
195
164
37
>>> pw=u"löä%"
>>> for i in pw:
...     ord(i)
... 
108
246
228
37
>>> 

Was auch immer dahintersteht - ich brauche dieses erstere seltsame Verhalten. Kann ich meinen unicode-String irgendwie dahingehend umwandeln?

Nochmal kurz: ich habe einen Unicode-String oö:

1
2
3
4
5
6
7
>>> pw=u"oö"
>>> pw
u'o\xf6'
>>> print pw

>>> len(pw)
2

und möchte daraus einen solchen erhalten:

1
2
3
4
5
6
7
>>> pw="oö"
>>> pw
'o\xc3\xb6'
>>> print pw

>>> len(pw)
3

EDIT2: hab's.

1
password = password.encode("UTF-8")

besorgt die Umwandlung.

Allerbesten Dank für die schnelle und effektive Hilfe!

Gruß David

Antworten |