web-dev-qa-db-fra.com

Problème de codage / décodage étrange en Base64

J'utilise Grails 1.3.7. J'ai du code qui utilise la fonction intégrée base64Encode et la fonction base64Decode. Tout fonctionne bien dans des cas de test simples où j'encode des données binaires, puis je décode la chaîne résultante et je l'écris dans un nouveau fichier. Dans ce cas, les fichiers sont identiques.

Mais ensuite, j'ai écrit un service Web qui a pris les données encodées en base64 comme paramètre dans un appel POST. Bien que la longueur des données base64 soit identique à la chaîne que j'ai passée dans la fonction, le contenu des données base64 sont en cours de modification. J'ai passé JOURS à déboguer ceci et j'ai finalement écrit un contrôleur de test qui a passé les données en base64 à poster et a également pris le nom d'un fichier local avec les bonnes données encodées en base64, comme dans:

data=AAA-base-64-data...&testFilename=/name/of/file/with/base64data

Dans la fonction de test, j'ai comparé chaque octet du paramètre de données entrantes avec l'octet approprié du fichier de test. J'ai trouvé que d'une manière ou d'une autre, chaque caractère "+" dans le paramètre de données d'entrée avait été remplacé par un "" (espace, ordinal ascii 32). Hein? Qu'est-ce qui aurait pu faire ça?

Pour être sûr que j'avais raison, j'ai ajouté une ligne qui disait:

data = data.replaceAll(' ', '+')

et bien sûr, les données ont été décodées exactement. Je l'ai essayé avec des fichiers binaires arbitrairement longs et cela fonctionne maintenant à chaque fois. Mais je ne peux pas comprendre pour la vie de moi ce qui modifierait le paramètre de données dans le message pour convertir le caractère ord (43) en ord (32)? Je sais que le signe plus est l'un des 2 caractères quelque peu dépendants de la plate-forme dans la spécification base64, mais étant donné que je fais l'encodage et le décodage sur la même machine pour l'instant, je suis très perplexe sur la cause de cela. Bien sûr, j'ai un "correctif" puisque je peux le faire fonctionner, mais je suis nerveux à propos des "correctifs" que je ne comprends pas.

Le code est trop gros pour être publié ici, mais j'obtiens l'encodage base64 comme suit:

def inputFile = new File(inputFilename)
def rawData =  inputFile.getBytes()
def encoded = rawData.encodeBase64().toString()

J'écris ensuite cette chaîne codée dans un nouveau fichier afin que je puisse l'utiliser pour des tests plus tard. Si je charge ce fichier de nouveau, j'obtiens le même rawData:

def encodedFile = new File(encodedFilename)
String encoded = encodedFile.getText()
byte[] rawData = encoded.decodeBase64()

Donc tout cela est bon. Supposons maintenant que je prenne la variable "encodée" et que je l'ajoute à un paramètre à une fonction POST comme ceci:

String queryString = "data=$encoded"
String url = "http://localhost:8080/some_web_service"

def results = urlPost(url, queryString)

def urlPost(String urlString, String queryString) {
    def url = new URL(urlString)
    def connection = url.openConnection()
    connection.setRequestMethod("POST")
    connection.doOutput = true

    def writer = new OutputStreamWriter(connection.outputStream)
    writer.write(queryString)
    writer.flush()
    writer.close()
    connection.connect()

    return (connection.responseCode == 200) ? connection.content.text : "error                         $connection.responseCode, $connection.responseMessage"
}

du côté du service Web, dans le contrôleur, j'obtiens le paramètre comme suit:

String data = params?.data
println "incoming data parameter has length of ${data.size()}" //confirm right size

//unless I run the following line, the data does not decode to the same source
data = data.replaceAll(' ', '+')

//as long as I replace spaces with plus, this decodes correctly, why?
byte[] bytedata = data.decodeBase64()

Désolé pour la longue diatribe, mais j'aimerais vraiment comprendre pourquoi j'ai dû faire le "remplacer l'espace par le signe plus" pour que cela décode correctement. Y a-t-il un problème avec le signe plus utilisé dans un paramètre de demande?

19
Rich Sadowsky

Tout ce qui remplit params s'attend à ce que la requête soit une forme encodée en URL (en particulier, application/x-www-form-urlencoded, Où "+" signifie espace), mais vous ne l'avez pas encodée en URL. Je ne sais pas quelles fonctions votre langage fournit, mais dans le pseudo code, queryString devrait être construit à partir de

concat(uri_escape("data"), "=", uri_escape(base64_encode(rawBytes)))

qui simplifie à

concat("data=", uri_escape(base64_encode(rawBytes)))

Les caractères "+" Seront remplacés par "%2B".

13
ikegami

Parce qu'il s'agit d'un paramètre pour un POST vous devez encoder l'URL des données.

Voir http://en.wikipedia.org/wiki/Percent-encoding

3
Richard Schneider

Vous devez utiliser un code base64 spécial qui est également sûr pour les URL. Le problème est que l'encode base64 standard inclut +, / et = caractères qui sont remplacés par la version codée en pourcentage.

http://en.wikipedia.org/wiki/Base64#URL_applications

J'utilise le code suivant en php:

    /**
     * Custom base64 encoding. Replace unsafe url chars
     *
     * @param string $val
     * @return string
     */
    static function base64_url_encode($val) {

        return strtr(base64_encode($val), '+/=', '-_,');

    }

    /**
     * Custom base64 decode. Replace custom url safe values with normal
     * base64 characters before decoding.
     *
     * @param string $val
     * @return string
     */
    static function base64_url_decode($val) {

        return base64_decode(strtr($val, '-_,', '+/='));

    }
3
Polak

paraquote depuis le lien wikipedia

Le codage utilisé par défaut est basé sur une version très ancienne des règles générales de codage de pourcentage d'URI, avec un certain nombre de modifications telles que la normalisation de nouvelle ligne et le remplacement des espaces par "+" au lieu de "% 20"

un autre écueil caché que les développeurs Web de tous les jours comme moi en savent peu

1
han