IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Python Débutant: Erreur encodage Latin-1 vs utf-8 lecture des mots d'un dico dans un fonction..


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre actif
    Homme Profil pro
    Amateur débutant
    Inscrit en
    Décembre 2019
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Amateur débutant

    Informations forums :
    Inscription : Décembre 2019
    Messages : 88
    Par défaut Python Débutant: Erreur encodage Latin-1 vs utf-8 lecture des mots d'un dico dans un fonction..
    Bonjour,

    Ben.. j'ai fais mon possible pour essayer d'y arriver seul.
    Mais j'ai besoin d'aide. Je tourne en rond.mais d'une force!!
    j'ai lu des sujets, posts, blogs sur les erreurs codage/décodage, latin-1 et utf-8, ASCII et autre unicode, bytes et BOM.. bref trop dur pour mon faible niveau pour trouver et comprendre comment corriger cet "étrange comportement" de ce programme.

    Voici la situation:
    Mon IDE (pour pyton 3.8) c'est Pyzo 4.9.0.
    Je suis sous windows 10.

    j'essai de faire un petit prog proposé dans ce bouquin: https://automatetheboringstuff.com/chapter13/
    Les fichiers, (dictionnary.txt et meetingminutes.pdf en l'occurrence) sont dans l'archive téléchargeable ici : https://nostarch.com/download/Automa...ematerials.zip

    Le programme doit retrouver le mot de passe d'un pdf (préalablement crypté avec ce mot de passe) en soumettant un à un les mots d'un dico (dictionary.txt) à la fonction decrypt() du pdf, puis afficher le mot si cette fonction retourne 1 / True.

    J'ai bricolé ça (merci de votre indulgence ) sous forme d'une fonction.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import PyPDF2   # importe le module pdf
     
     
    def pdfDicoSearchPW(pdfName):
     
        pdfFileObj = open(pdfName, 'rb')  # ouvrir le pdf en lecture binaire
        pdfReader = PyPDF2.PdfFileReader(pdfFileObj)  # creation d'un objet reader
     
        if pdfReader.isEncrypted==True:  # si le pdf est crypté
            dictionnaire="dictionary.txt" #dictionnaire anglais majuscules
            with open(dictionnaire, "r", encoding='utf-8') as file:
                data = file.read()  # placer le contenu de la lecture du dico dans data
                for each in data.split():   # pour chaque mot du texte splité.
                    if pdfReader.decrypt(each)==True: # si c'est 1 (True)
                        return each # retourne le mot trouvé dans le dico et arrete le prog
    Alors:
    Pour le "dictionary.txt" (UTF-8 écrit en bas à droite de note pad), structuré avec des mots anglais en majuscule allant de:

    AARHUS
    AARON
    ..
    ..
    ZULUS
    ZURICH

    Si je teste un pdf crypté par mot de passe ABBY, COULD, KNOX, SEND, etc.. ça fonctionne..
    Si je teste avec un pdf crypté par l'avant dernier mot ZULUS ça fonctionne encore..

    Par contre, quand je teste avec un pdf crypté par mot de passe ZURICH (dernier mot du dico), dès le lancement du programme, apparaît ce message d'erreur.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    >>> pdfDicoSearchPW('Encrypted-ZURICH.pdf')
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "C:\Users\Gillian\Desktop\Informatique\Programmation\Python\JmaWorkSpace\PublieDeveloppez-PdfBrutForceDico.py", line 16, in pdfDicoSearchPW
        if pdfReader.decrypt(each)==True: # si c'est 1 (True)
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\pdf.py", line 1987, in decrypt
        return self._decrypt(password)
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\pdf.py", line 2017, in _decrypt
        val = utils.RC4_encrypt(new_key, val)
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\utils.py", line 181, in RC4_encrypt
        retval += b_(chr(ord_(plaintext[x]) ^ t))
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\utils.py", line 238, in b_
        r = s.encode('latin-1')
    UnicodeEncodeError: 'latin-1' codec can't encode character '\u20b7' in position 0: ordinal not in range(256)
    Je comprends pas pourquoi ça fonctionne sur toute la liste mais plus sur le dernier mot du dico qui n'est pourtant pas accentué?

    Note:----------

    Par ailleurs, j'ai testé un autre "Dico-FR-minuscule.txt" français (écrit en minuscules avec accents et en UTF-8 (vu sur notepad)).

    aaron
    abaissé
    abaissement
    ..
    Hélène
    ..
    Zurich
    zygote

    Avec un fichier pdf crypté par mot de passe Hélène.
    Le programme se lance, cherche une minute... et d'un coup renvoie ce même type de message d'erreur sans avoir trouvé.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    >>> pdfDicoSearchPW('Encrypted-Hélène.pdf')
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "C:\Users\Gillian\Desktop\Informatique\Programmation\Python\JmaWorkSpace\PublieDeveloppez-PdfBrutForceDico.py", line 16, in pdfDicoSearchPW
        if pdfReader.decrypt(each)==True: # si c'est 1 (True)
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\pdf.py", line 1987, in decrypt
        return self._decrypt(password)
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\pdf.py", line 1997, in _decrypt
        user_password, key = self._authenticateUserPassword(password)
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\pdf.py", line 2036, in _authenticateUserPassword
        U, key = _alg35(password, rev,
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\pdf.py", line 2973, in _alg35
        key = _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry)
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\pdf.py", line 2866, in _alg32
        password = b_((str_(password) + str_(_encryption_padding))[:32])
      File "c:\users\gillian\appdata\local\programs\python\python38-32\lib\site-packages\PyPDF2\utils.py", line 238, in b_
        r = s.encode('latin-1')
    UnicodeEncodeError: 'latin-1' codec can't encode character '\u0153' in position 7: ordinal not in range(256)
    J'en déduis qu'il y a des erreurs d'encodage/décodage entre latin-1 et utf-8 sur certain mots de ces dictionnaires et/ou dans mon code.
    Mais trop ardu ..et je voudrai comprendre.

    Please Help me

  2. #2
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Bonjour,

    Je viens de plonger un peu dans le code source de PyPDF2 et j'en ai conclu que ce package était bogué. Peut-être qu'il y a aussi un problème dans ce que tu lui donnes en entrée, mais alors tu aurais dû recevoir un message d'erreur plus clair.

    Dans le cas de ton pdf crypté par le mot de passe ZURICH, les fonctions qui apparaissent dans la pile d'appel ont le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
        def decrypt(self, password):
            """
            When using an encrypted / secured PDF file with the PDF Standard
            encryption handler, this function will allow the file to be decrypted.
            It checks the given password against the document's user password and
            owner password, and then stores the resulting decryption key if either
            password is correct.
            It does not matter which password was matched.  Both passwords provide
            the correct decryption key that will allow the document to be used with
            this library.
            :param str password: The password to match.
            :return: ``0`` if the password failed, ``1`` if the password matched the user
                password, and ``2`` if the password matched the owner password.
            :rtype: int
            :raises NotImplementedError: if document uses an unsupported encryption
                method.
            """
     
            self._override_encryption = True
            try:
                return self._decrypt(password)
            finally:
                self._override_encryption = False
     
        def _decrypt(self, password):
            encrypt = self.trailer['/Encrypt'].getObject()
            if encrypt['/Filter'] != '/Standard':
                raise NotImplementedError("only Standard PDF encryption handler is available")
            if not (encrypt['/V'] in (1, 2)):
                raise NotImplementedError("only algorithm code 1 and 2 are supported. This PDF uses code %s" % encrypt['/V'])
            user_password, key = self._authenticateUserPassword(password)
            if user_password:
                self._decryption_key = key
                return 1
            else:
                rev = encrypt['/R'].getObject()
                if rev == 2:
                    keylen = 5
                else:
                    keylen = encrypt['/Length'].getObject() // 8
                key = _alg33_1(password, rev, keylen)
                real_O = encrypt["/O"].getObject()
                if rev == 2:
                    userpass = utils.RC4_encrypt(key, real_O)
                else:
                    val = real_O
                    for i in range(19, -1, -1):
                        new_key = b_('')
                        for l in range(len(key)):
                            new_key += b_(chr(utils.ord_(key[l]) ^ i))
                        val = utils.RC4_encrypt(new_key, val)
                    userpass = val
                owner_password, key = self._authenticateUserPassword(userpass)
                if owner_password:
                    self._decryption_key = key
                    return 2
            return 0
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def RC4_encrypt(key, plaintext):
        S = [i for i in range(256)]
        j = 0
        for i in range(256):
            j = (j + S[i] + ord_(key[i % len(key)])) % 256
            S[i], S[j] = S[j], S[i]
        i, j = 0, 0
        retval = []
        for x in range(len(plaintext)):
            i = (i + 1) % 256
            j = (j + S[i]) % 256
            S[i], S[j] = S[j], S[i]
            t = S[(S[i] + S[j]) % 256]
            retval.append(b_(chr(ord_(plaintext[x]) ^ t)))
        return b_("").join(retval)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    if sys.version_info[0] < 3:
        def b_(s):
            return s
    else:
        B_CACHE = {}
     
        def b_(s):
            bc = B_CACHE
            if s in bc:
                return bc[s]
            if type(s) == bytes:
                return s
            else:
                r = s.encode('latin-1')
                if len(s) < 2:
                    bc[s] = r
                return r
    En lisant le code source, la fonction RC4_encrypt attend un paramètre plaintext dont chaque élément représente un octet. La fonction accepte que plaintext soit de type bytes ou str. Mais, en Python 3, si c'est str, alors il faut que chaque caractère ait une valeur entre 0 et 256.

    Dans ton exécution, plaintext contient un caractère Unicode U+20b7 qui n'est pas convertible en Latin-1.

    Peut-être que le code de PyPDF2 a oublié quelque part une conversion de str vers bytes.

    Ou alors, peut-être qu'il y a un problème avec les données que tu donnes à PyPDF2 mais alors, dans le code source de PyPDF2, le problème aurait dû être détecté bien avant de rentrer dans la fonction RC4_encrypt. Quand une donnée est erronée, si elle se balade un peu partout dans le code avant d'arriver dans une fonction qui détecte l'erreur, c'est long et ennuyeux d'analyser beaucoup de code pour remonter à la source.

    J'ai regardé rapidement les tests unitaires de PyPDF2, mais je vois qu'il y en a trop peu par rapport au volume du code. En plus, le code n'est plus maintenu depuis plusieurs années.

    Du coup, je pense que le plus simple est de laisser tomber et de passer à la suite du livre.

    Remarque : il faudrait remplacer pdfReader.decrypt(each)==True par pdfReader.decrypt(each)==1. Cela ne corrigera pas ton problème, mais ce sera plus propre, car decrypt te renvoie 0, 1 ou 2.

  3. #3
    Membre actif
    Homme Profil pro
    Amateur débutant
    Inscrit en
    Décembre 2019
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Amateur débutant

    Informations forums :
    Inscription : Décembre 2019
    Messages : 88
    Par défaut
    Bonjour Pyramidev,
    Merci pour ton temps passé sur le sujet.

    Citation Envoyé par Pyramidev Voir le message
    Remarque : il faudrait remplacer pdfReader.decrypt(each)==True par pdfReader.decrypt(each)==1. Cela ne corrigera pas ton problème, mais ce sera plus propre, car decrypt te renvoie 0, 1 ou 2.
    Bon ben ça déjà c'est fait..

  4. #4
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    J'utilise beaucoup PyPDF2, et depuis longtemps, pour construire un fichier pdf à partir de plusieurs fichiers pdf ou le contraire. Mais je n'ai jamais utilisé avec mot de passe.

    A mon avis, dès qu'on commence à travailler avec des fonctions de cryptage, on voit bien que les chaînes de caractères destinées à ce cryptage sont forcément des "bytes". Si la doc du module dit qu'elle accepte des str, c'est qu'il y a une conversion qui nécessite un encodage particulier. Si l'encodage prévu dans le module est "latin-1" et qu'on envoie du "utf-8", ça ne peut pas marcher. Mais, bien sûr, ça n'explique pas pourquoi ça coince avec "ZURICH".

  5. #5
    Membre actif
    Homme Profil pro
    Amateur débutant
    Inscrit en
    Décembre 2019
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Amateur débutant

    Informations forums :
    Inscription : Décembre 2019
    Messages : 88
    Par défaut
    Bonjour,
    Merci tyrtamos,
    Citation Envoyé par tyrtamos Voir le message
    Bonjour,

    J'utilise beaucoup PyPDF2, et depuis longtemps, pour construire un fichier pdf à partir de plusieurs fichiers pdf ou le contraire. Mais je n'ai jamais utilisé avec mot de passe.

    A mon avis, dès qu'on commence à travailler avec des fonctions de cryptage, on voit bien que les chaînes de caractères destinées à ce cryptage sont forcément des "bytes". Si la doc du module dit qu'elle accepte des str, c'est qu'il y a une conversion qui nécessite un encodage particulier. Si l'encodage prévu dans le module est "latin-1" et qu'on envoie du "utf-8", ça ne peut pas marcher. Mais, bien sûr, ça n'explique pas pourquoi ça coince avec "ZURICH".
    Et si ça pouvait venir du code qui crypte le pdf ?
    A toutes fins utiles le voici:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    def pdfEncrypt(pdfName, encryptedOutputName):
        pdfFile = open(pdfName, 'rb')
        pdfReader = PyPDF2.PdfFileReader(pdfFile)
        pdfWriter = PyPDF2.PdfFileWriter()
     
        for pageNum in range(pdfReader.numPages):
            pdfWriter.addPage(pdfReader.getPage(pageNum))
     
        password = input("\nQuel mot de passe désirez vous pour crypter ce document pdf?\n>  ")
     
        pdfWriter.encrypt(password)
        resultPdf = open(encryptedOutputName, 'wb')
        pdfWriter.write(resultPdf)
        resultPdf.close()
        pdfFile.close()
        print(f"\nFichier {encryptedOutputName} créé et crypté avec succès. N'oubliez pas votre mot de passe!")
    J'ai re-(re)^n-fais un cryptage du fichier "meetingminutes.pdf" (qui est dans l'archive fournie avec le livre) en "Encrypted-ZURICH.pdf" avec cette fonction pdfEncrypt().

    Puis j'applique sur cet "Encrypted-ZURICH.pdf" la fonction de criblage par dictionnaire et on obtient toujours le même message d'erreur immédiat.

    Alors qu'avec cette même procédure de cryptage pour "Encrypted-ZULUS.pdf", ça prend ensuite plus de 10 minutes à a fonction de criblage..mais elle fini par retrouver ZULUS.
    Étrange non? Que ce soit le dernier mot de la liste (ZURICH) et que l'erreur arrive immédiatement après le lancement...

    Cela dit, dans le cas du mot de passe Hélène (autre dico), l'erreur arrive après une minute de criblage...

Discussions similaires

  1. demande explication sur un message d'erreur, en python débutant
    Par amateurprg dans le forum Général Python
    Réponses: 21
    Dernier message: 09/11/2019, 19h30
  2. Erreur encodage UTF-8
    Par Johngtrs dans le forum PostgreSQL
    Réponses: 3
    Dernier message: 08/06/2012, 14h59
  3. Erreur encodage Sql Server UTF-8
    Par Nico820 dans le forum Outils
    Réponses: 2
    Dernier message: 18/01/2011, 16h24
  4. Erreur encodage UTF-8
    Par dafalri dans le forum Langage
    Réponses: 8
    Dernier message: 02/12/2008, 15h44
  5. Réponses: 2
    Dernier message: 10/10/2006, 12h38

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo