softmixx background

Web Crypto API und openssl

Javascript, PHP, Full Stack Development

Asymmetrische Verschlüsselung - sign/verify mit JavaScript und PHP

[ 15.11.2021 | Alex]
Die Anwendung und Intregration von Verschlüsselungsverfahren und Kryptographie in Webprojekte ist nicht erst seit seit OAuth und REST API's Standardinstrumentarium für Webentwickler. Dennoch ist das Thema ein sehr komplexes und diffizil, schon allein aufgrund der Vielfalt an Algorithmen und Formaten.

Gut, wenn man dann ein paar Code Beispiele zur Hand hat, die in der Praxis die Implementierung erleichtern. In der daybee App prüfe ich anhand eines Public/Private Key Verfahrens, ob der Urheber einer Nachricht an den Webserver tatsächlich mit dem angegebenen Absender übereinstimmt. Dazu wird die Nachricht mit einem zuvor generierten privaten Schlüssel signiert. Der Webserver validiert dann anhand des passenden öffentlichen Schlüssels die Signatur der Nachricht. 

Das Schlüsselpaar wird in der App via Web Crypto API erstellt:

/*
 * genSignKeyPair
 *
**/

beeSecure.genSignKeyPair = async function(){

 await window.crypto.subtle.generateKey( {

  name: beeSecure.algo, // 'RSASSA-PKCS1-v1_5'
  modulusLength: 2048,
  publicExponent: new Uint8Array([1, 0, 1]),
  hash: beeSecure.hash //'SHA-256'
     },
  true,
  ["sign", "verify"]
 
 ).then((keyPair) => {

  beeOptions.sign_keys[BEE_SECURE_SIGN_PRIVATE_KEY] = keyPair.privateKey;
  beeOptions.sign_keys[BEE_SECURE_SIGN_PUBLIC_KEY] = keyPair.publicKey;

  storage.set( {'options' : beeOptions } );

 });

Den öffentlichen Schlüssel (public key) übertrage ich im Vorfeld an den Webserver im PEM-Format:

  const exportedKey = await window.crypto.subtle.exportKey( "spki", beeOptions.sign_keys[BEE_SECURE_SIGN_PUBLIC_KEY] );
  const publickeyPEMString = window.btoa(String.fromCharCode.apply(null, new Uint8Array(exportedKey )));

Eine Client Nachricht der App an den Webserver wird nun mit dem privaten Schlüssel signiert. Dazu erstelle ich ein Signatur Token, dass z.B. ein Digest der Nachricht sein kann:

 let encoded = new TextEncoder().encode(beeUser.sigtoken);

 let signature = await window.crypto.subtle.sign(
  "RSASSA-PKCS1-v1_5",
  beeOptions.sign_keys[BEE_SECURE_SIGN_PRIVATE_KEY],
  encoded
 );

Signatur und Token sind dann beides Request Header Parameter:

   "X-DWT-SIG": window.btoa(String.fromCharCode.apply(null, new Uint8Array(signature))),
   "X-DWT-TOKEN": beeUser.sigtoken

In PHP am Server läßt sich die Signatur nun auf Echtheit mit der openssl Erweiterung prüfen:

$pem = "-----BEGIN PUBLIC KEY-----\n". $user_stored_public_key . "\n-----END PUBLIC KEY-----";

$is_token_verified = openssl_verify($_SERVER['HTTP_X_DWT_TOKEN'], base64_decode($_SERVER['HTTP_X_DWT_SIG']), $pem,"sha256WithRSAEncryption" );

 

Ist die Signatur korrekt, wird $is_token_verified den Wert 1 haben.