Hashwert zu kurz

Herr_M

Erfahrenes Mitglied
Hallo zusammen,

Ich versuche gerade ein Programm zu schreiben, dass auf einen Webservice zugreift und sich dort mittels eines public und eines Secret Keys authentifziert.
Der Aufbau der Verbindung etc klappt schon nur mit der Berechnung des Hashwertes für den Secret Key gibt es Probleme.
Laut Anbieter des Webservices ist der Key der dort ankommt zu kurz?!

In der API des Webservice ist angegeben, dass die Signatur (also der gehashte/geheime Teil wie folgt zu berechnen ist.

Signatur = base64_encode(sha1(SignaturString + secret_key)

Wenn ich das richtig verstanden habe soll von "SignaturString + secret_key" ein Hashwert berechnet werden unter Verwendung des SHA1 Algorithmus und dieser Hashwert dann nochmals mit Base64 encodiert oder?

Habe das wie folg in JAVA gelöst... scheint aber das falsche Ergebnis zu liefern, da angeblich zu kurz?
Vom Prinzip her macht das Programm doch genau das was spezifiziert ist oder etwa nicht? Hab ich da irgendwo einen Prinzipiellen Fehler drin, der mir jetzt so nicht auffällt oder hab ich was vergessen?

Java:
package de.verratichnicht.pack;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;

public class Utils {


   public static final String TIMESTAMP_PATTERN = "EEE, dd MMM yyyy HH:mm:ss Z";
   public static final String TIMEZONE = "Europe/Berlin";
   public static final String UTF_8 = "UTF-8";
   public static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

   /**
    * @param method
    * @param resource
    * @param datestring
    * @param secretKey
    * @return
    */
   public static final String generateSignature(String method,
                                                String resource,
                                                String datestring,
                                                String secretKey) {

      Logger log = Logger.getLogger("de.avs.marktjagd.client.utilities.Utils");

      log.debug("-------------------------------------------");
      log.debug("method: " + method);
      log.debug("resource: " + resource);
      log.debug("datestring: " + datestring);
      log.debug("secretKey: " + secretKey);

      String signature = "";

      // build the string to encode
      final StringBuffer signatureString = new StringBuffer();
      signatureString.append(method);
      signatureString.append(resource);
      signatureString.append(datestring);

      final SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(),
                                                         HMAC_SHA1_ALGORITHM);

      // Acquire the MAC instance and initialize with the signing key.
      Mac mac = null;
      try {
         mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
      }
      catch( final NoSuchAlgorithmException e ) {
         throw new RuntimeException("Could not find sha1 algorithm", e);
      }
      try {
         mac.init(signingKey);
      }
      catch( final InvalidKeyException e ) {
         throw new RuntimeException("Could not initialize the MAC algorithm", e);
      }

      // Compute the HMAC on the digest, and set it.
      final String canonicalString = convertUTF8(signatureString.toString());

      try {
         signature = Base64.encodeBase64URLSafeString(mac.doFinal(canonicalString.getBytes(UTF_8)));
      }
      catch( final IllegalStateException e ) {
         signature = null;
         e.printStackTrace();
      }
      catch( final UnsupportedEncodingException e ) {
         signature = null;
         e.printStackTrace();
      }

      log.debug("Signature: " + signature);
      log.debug("Signature length: " + signature.length());
      log.debug("-------------------------------------------");

      return signature;
   }

   /**
    * Convert String to UTF-8
    * 
    * @param string
    *           input string
    * @return input string converted to UTF-8 encoding
    */
   public static String convertUTF8(final String string) {
      try {
         return new String(string.getBytes(UTF_8), UTF_8);
      }
      catch( final UnsupportedEncodingException e ) {
         e.printStackTrace();
         return null;
      }

   }

   /**
    * Erstellen eines passenden Zeitstempels
    * 
    * @return
    */
   public static String createRestTimestamp() {

      Logger log = Logger.getLogger("de.avs.marktjagd.client.utilities.Utils");

      final SimpleDateFormat df = new SimpleDateFormat(TIMESTAMP_PATTERN, Locale.US);
      df.setTimeZone(TimeZone.getTimeZone(TIMEZONE));
      final String date = df.format(new Date());
      return convertUTF8(date);
   }
}
 
Ich blicke deinen Code nicht ganz. Ich finde nichtmal die Stelle, wo du SignaturString und secret_key konkatenierst. Vielleicht hast du die Variablen auch anders benannt.

Ich hab hier mal ein Beispielcode.

Java:
import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;

class SHA {
    public static void main(String[] args) {
        String signaturString = "foo", secret_key = "bar";
        System.out.println(getBase64Encoded(getSHA1(signaturString+secret_key)));
	}

    private static byte[] getSHA1(String s) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(s.getBytes());
            byte[] hash = digest.digest();
            return hash;
        } catch (NoSuchAlgorithmException ex) {
            return null;
        }
    }

    public static String getBase64Encoded(byte[] b)
	{
		char[] map1 = new char[] {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'};

		int iLen = b.length;
		int oDataLen = (iLen*4+2)/3;       // output length without padding
		int oLen = ((iLen+2)/3)*4;         // output length including padding
		char[] out = new char[oLen];
		int ip = 0;
		int op = 0;
		while (ip < iLen)
		{
			int i0 = b[ip++] & 0xff;
			int i1 = ip < iLen ? b[ip++] & 0xff : 0;
			int i2 = ip < iLen ? b[ip++] & 0xff : 0;
			int o0 = i0 >>> 2;
			int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
			int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
			int o3 = i2 & 0x3F;
			out[op++] = map1[o0];
			out[op++] = map1[o1];
			out[op] = op < oDataLen ? map1[o2] : '='; op++;
			out[op] = op < oDataLen ? map1[o3] : '='; op++;
		}
		return String.valueOf(out);
	}
}
 
Hi.

So wie ich das verstehe solltest du einfach einen SHA1 Hash des SignatureString konkateniert mit dem Secret_Key berechnen. Aber irgendwie fehlt da auch eine Klammer in deinem Ausdruck. Ist die API Doku des Webservice denn irgendwo zugänglich? Wie ist die genaue Fehlermeldung / Exception?

Aber egal wie du den Hashwert berechnest, ein SHA1 Hash ist immer 20 Byte lang. Deshalb vermute ich mal, das der Algorithmus so ausschauen sollte:
Code:
Signatur = base64_encode(sha1(SignaturString) + secret_key)

Übrigens, deine convertUTF8 Methode ist eine no-Op. Du dekodierst die Daten des Strings nach UTF-8, und enkodierst diese Daten dann von UTF-8 wieder in einen String (welcher immer intern UTF-16 kodiert ist).

Gruß
 
Hi ihr beiden,

Erstmal Danke für eure Antworten,

Die API des Webservices ist leider nicht öffentlich zugänglich sondern nur für zahlende (Firmen)Kunden und ich bin Softwareentwickler bei einer solchen zahlenden Firma.

@deepthroat: Ja da fehlt meiner Meinung nach auch eine Klammer, aber die fehlt auch in der Dokumentation zum Webservice, ich werd diesbezüglich nochmal beim Support des Anbieters nachfragen wo diese denn nun wirklich hin soll.

@CPoly: Danke für das Codebeispiel, aber leider ist der damit erzeugte Hash "wieder" zu kurz?

Zitat Support des Webservice Anbieters
Ihr Schlüssel den Sie uns schicken scheint auch zu kurz zu sein. Als Authentifizierung senden Sie uns zum Beispiel:
123456789012345:Pr2x8JIxYtmkEK2p_l9ntd8cwaw

Eine korrekte Authentifizierung müsste für folgt aussehen (base64 encodeter SHA1-Hash):
123456789012345:NDdiNzgyYmZjM2I1OREGNzJkMWZiODAxN2E4Y2ZiOTU4MGJlZjUyNg==

Der SHA1-Hash müsste immer eine Länge von 40 Zeichen haben, zum Beispiel:
47b782bfc3bffea72d1fb8017a8cfb9580bef526

Der Teil vor dem ":" ist der Public Key (natürlich nicht der echte) der Teil nach dem ":" die Signatur (also der base64 encodierte SHA1) die ich versuche zu generieren.

Wie ich gerade erfahren habe nutzt die Gegenseite zu meiner Anfrage (also der Webservice) PHP und die die SHA1 Funktion von PHP
und diese liefert per default 40 Bytes Hexwerte und nicht wie JAVA 20 Byte Binary....

Noch so ein Grund weshalb ich PHP manchmal zum Fenster raus haun könnte...

So wie krieg ich jetzt meine 20 Bytes Binary auf die 40 Bytes Hex? Weil den Webservice kann ich leider nicht auf die 20 Bytes Binary umstellen (auch wenn PHP das theoretisch könnte)

http://au.php.net/manual/en/function.sha1.php
 
Zuletzt bearbeitet:
Die erwarten also, dass du den SHA1 Wert in Hexadezimalschreibweise Codierst und nicht in reinform.
Ist jetzt etwas stark verschachtelt, hab aber keine Geduld es schöner zu machen :-D
Java:
import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;

class SHA {
    public static void main(String[] args) {
        String signaturString = "foo", secret_key = "bar";
        System.out.println(
                getBase64Encoded(
                    getHex(
                        getSHA1(signaturString+secret_key)).getBytes()));
	}
	
	private static final String HEXES = "0123456789abcdef";

    private static byte[] getSHA1(String s) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(s.getBytes());
            byte[] hash = digest.digest();
            return hash;
        } catch (NoSuchAlgorithmException ex) {
            return null;
        }
    }

    private static String getHex( byte [] raw ) {
        if ( raw == null ) return null;

        final StringBuilder hex = new StringBuilder( 2 * raw.length );
        for ( final byte b : raw ) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4))
                .append(HEXES.charAt((b & 0x0F)));
        }
        return hex.toString();
    }

    public static String getBase64Encoded(byte[] b)
	{
		char[] map1 = new char[] {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'};

		int iLen = b.length;
		int oDataLen = (iLen*4+2)/3;       // output length without padding
		int oLen = ((iLen+2)/3)*4;         // output length including padding
		char[] out = new char[oLen];
		int ip = 0;
		int op = 0;
		while (ip < iLen)
		{
			int i0 = b[ip++] & 0xff;
			int i1 = ip < iLen ? b[ip++] & 0xff : 0;
			int i2 = ip < iLen ? b[ip++] & 0xff : 0;
			int o0 = i0 >>> 2;
			int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
			int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
			int o3 = i2 & 0x3F;
			out[op++] = map1[o0];
			out[op++] = map1[o1];
			out[op] = op < oDataLen ? map1[o2] : '='; op++;
			out[op] = op < oDataLen ? map1[o3] : '='; op++;
		}
		return String.valueOf(out);
	}
}

Das was du denen sendest, kann übrigens kein Base64 sein, weil es nicht mit einem Gleichheitszeichen endet.
 
Die erwarten also, dass du den SHA1 Wert in Hexadezimalschreibweise Codierst und nicht in reinform.
Ist jetzt etwas stark verschachtelt, hab aber keine Geduld es schöner zu machen :-D
Das macht nix, bin ja schon froh, dass du dir überhaupt die Zeit nimmst so ausführlich zu antworten.

Das was du denen sendest, kann übrigens kein Base64 sein, weil es nicht mit einem Gleichheitszeichen endet.
Aha! Wußt ich garnicht, dass sowas immer auf "=" endet...
Aber was zum Geier macht dann die Base64 Klasse von Apache... die Leute da sind ja eigentlich Profis, dies können... hmm...
Mal sehn obs mit deiner Methode geht.

Danke erstmal... so jetzt erstmal ausprobieren.
 
Ich muss mich für mein Halbwissen entschuldigen. Ich hatte das falsch in Erinnerung ;). Es endet nicht zwangsläufig auf =.

Hmm ok... tuts aber fast immer wies scheint. Hab jetzt jedenfalls mal die komplette Verschlüsselung durch deine selbstebauten Methoden ersetzt und auf einmal geht's!
Und hmm die Base64 encodierten Sachen hatten bisher immer ein "=" am Ende... wär mir also erstmal nicht aufgefallen :-)
Aber gut das dus nochmal erwähnst, sonst hätte ich das wohl für bare Münze genommen und später mal wenn ich wieder mal Base64 encoding nutzen muss, dann
immer nach Fehlern gesucht die garnicht da sind :)

Der Webservice akzeptiert jetzt jedenfalls meine Signatur und lässt eine Abfrage von Daten zu. Danke nochmal für die Mühe!
 
Zurück