JavaScript laden und SHA-256

14

Dies ist ein Code-Golf-Puzzle mit einer realen Anwendung. Einige aktuelle Browser, wenn Sie eine URL eingeben, die aussieht wie

data:text/html,<script>alert("hi")</script>

führt den angegebenen JavaScript-Code aus. Angenommen, Sie hatten eine URL, die wie folgt aussah (Pseudocode):

data:text/html,<script>
    myPublicKey="12345678";
    cryptoLib=download("http://example.com/somecryptolib.js");
    if(sha256sum(cryptoLib) == "12345678")
        eval(cryptoLib)
</script>

Wenn Sie dies auf Visitenkarten als QR-Code gedruckt haben erhält jeder, der diese URL mit einem geeigneten Browser aufruft, einen Krypto-Client mit öffentlichem Schlüssel, auf dem Ihr öffentlicher Schlüssel vorinstalliert ist, ohne dass etwas installiert werden muss. Aufgrund der Hash-Überprüfung können Sie sicher sein, dass sie die echte Krypto-Software haben, auch wenn sich ihr ISP in den Datenverkehr einmischt.

Leider ist die reale Version dieses Pseudocodes für einen QR-Code ziemlich lang. Meine Herausforderung lautet: Wie kurz kannst du es schaffen? Eine Implementierung würde:

  • Seien Sie ein Daten: ... URL, die korrekt von der Adressleiste von Chrome und Firefox ausgeführt wird. (Um eine gültige Daten-URL zu erstellen, müssen Sie% als% 25 kodieren und Zeilenumbrüche entfernen.)
  • Habe eine URL und einen SHA-256 Hash eingebettet, vorzugsweise als Klartext-String-Literale am Anfang
  • Laden Sie eine Datei mithilfe von XMLHttpRequest von einer URL herunter (oder einer ähnlichen API) . (Beachten Sie, dass der Server einen Access-Control-Allow-Origin: * -Header enthalten muss, damit dies funktioniert.)
  • Wenn diese URL erfolgreich geladen wurde und das Ergebnis eine Datei mit dem erwarteten Hash ist, führen Sie eval () aus. Sonst nichts tun oder eine Fehlermeldung anzeigen.
  • Alle integrierten JavaScript-Funktionen, die sowohl in Chrome als auch in Firefox vorhanden sind, sind ein faires Spiel, aber das Laden von Bibliotheken ist nicht möglich.
  • Verwenden Sie so wenig Bytes wie möglich

Ich habe eine naive Version mit CryptoJS ( verkleinerte Version ) erstellt:

data:text/html,<script>
    u = 'http://localhost:8000'
    h = '5e3f73c606a82d68ef40f9f9405200ce24adfd9a4189c2bc39015345f0ee46d4'
    // Insert CryptoJS here
    r = new XMLHttpRequest;
    r.open('GET', u, false);
    r.send();
    if(CryptoJS.SHA256(r.response) == h)
        eval(r.response);
</script>

Welches kommt aus dem Minifier als:

data:text/html,<script>u="http://localhost:8000";h="5e3f73c606a82d68ef40f9f9405200ce24adfd9a4189c2bc39015345f0ee46d4";var CryptoJS=CryptoJS||function(k,w){var f={},x=f.lib={},g=function(){},l=x.Base={extend:function(a){g.prototype=this;var c=new g;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},t=x.WordArray=l.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=w?c:4*a.length},toString:function(a){return(a||y).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%254)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%254)&255)<<24-8*((b+e)%254);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<32-8*(c%254);a.length=k.ceil(c/4)},clone:function(){var a=l.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push((1<<30)*4*k.random()|0);return new t.init(c,a)}}),z=f.enc={},y=z.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%254)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b,2),16)<<24-4*(b%258);return new t.init(d,c/2)}},m=z.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%254)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%254);return new t.init(d,c)}},n=z.Utf8={stringify:function(a){try{return decodeURIComponent(escape(m.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return m.parse(unescape(encodeURIComponent(a)))}},B=x.BufferedBlockAlgorithm=l.extend({reset:function(){this._data=new t.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=n.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?k.ceil(f):k.max((f|0)-this._minBufferSize,0);a=f*e;b=k.min(4*a,b);if(a){for(var p=0;p<a;p+=e)this._doProcessBlock(d,p);p=d.splice(0,a);c.sigBytes-=b}return new t.init(p,b)},clone:function(){var a=l.clone.call(this);a._data=this._data.clone();return a},_minBufferSize:0});x.Hasher=B.extend({cfg:l.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){B.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new A.HMAC.init(a,d)).finalize(c)}}});var A=f.algo={};return f}(Math);(function(k){for(var w=CryptoJS,f=w.lib,x=f.WordArray,g=f.Hasher,f=w.algo,l=[],t=[],z=function(a){return (1<<30)*4*(a-(a|0))|0},y=2,m=0;64>m;){var n;a:{n=y;for(var B=k.sqrt(n),A=2;A<=B;A++)if(!(n%25A)){n=!1;break a}n=!0}n&&(8>m&&(l[m]=z(k.pow(y,0.5))),t[m]=z(k.pow(y,1/3)),m++);y++}var a=[],f=f.SHA256=g.extend({_doReset:function(){this._hash=new x.init(l.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],p=b[2],k=b[3],s=b[4],l=b[5],m=b[6],n=b[7],q=0;64>q;q++){if(16>q)a[q]=c[d+q]|0;else{var v=a[q-15],g=a[q-2];a[q]=((v<<25|v>>>7)^(v<<14|v>>>18)^v>>>3)+a[q-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+a[q-16]}v=n+((s<<26|s>>>6)^(s<<21|s>>>11)^(s<<7|s>>>25))+(s&l^~s&m)+t[q]+a[q];g=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&p^f&p);n=m;m=l;l=s;s=k+v|0;k=p;p=f;f=e;e=v+g|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+p|0;b[3]=b[3]+k|0;b[4]=b[4]+s|0;b[5]=b[5]+l|0;b[6]=b[6]+m|0;b[7]=b[7]+n|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes;d[e>>>5]|=128<<24-e%2532;d[(e+64>>>9<<4)+14]=k.floor(b/(1<<30)*4);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=g.clone.call(this);a._hash=this._hash.clone();return a}});w.SHA256=g._createHelper(f);w.HmacSHA256=g._createHmacHelper(f)})(Math);r=new XMLHttpRequest;r.open("GET",u,!1);r.send();CryptoJS.SHA256(r.response)==h&&eval(r.response)</script> 

Getestet mit diesem minimalen Python-Server:

import BaseHTTPServer

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_HEAD(s):
        s.send_response(200)
        s._sendHeaders()
        s.end_headers()
    def do_GET(s):
        s.send_response(200)
        s._sendHeaders()
        s.end_headers()
        s.wfile.write('alert("Success!")')
    def _sendHeaders(s):
        s.send_header("Content-type", "script/javascript");
        s.send_header("Access-Control-Allow-Origin", "*");

def run(server_class=BaseHTTPServer.HTTPServer,
    handler_class=RequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.serve_forever()

run()

Der JavaScript-Anteil beträgt 4700 Byte, kann aber viel kleiner sein. Wie klein kann es werden?

Jimrandomh
quelle
Interessante Frage. Das Senden der Kryptobibliothek vom Server an den Client evalund das anschließende Senden einer zweiten Anforderung mit der geladenen Kryptobibliothek würde den Sinn dieses Problems zunichte machen. Ich habe einige Zeit an einer Lösung gearbeitet und festgestellt, dass es das Vertrauen des Internetdienstanbieters bedeutet, sich nicht mit der Kryptobibliothek herumzuschlagen, was ein Problem darstellt.
JayQuerie.com
Richtig; Um sicher zu sein, muss der Hash der zuerst geladenen Datei mit Code validiert werden, der vollständig in der URL enthalten ist. Glücklicherweise benötigt dies nur eine Hash-Funktion (die jedoch nicht unbedingt SHA-256 sein muss), die 4700 Bytes naiv war und bei starker Optimierung wahrscheinlich <1 KB sein könnte. (Das übernimmt dann das Laden und Überprüfen einer größeren Bibliothek, wobei die Größe keine Rolle spielt, da sie nicht mehr in einer URL enthalten ist.)
Jimrandomh
"Laden von Bibliotheken ist unmöglich." - Sollte das Laden von Bibliotheken nicht erlaubt sein, es sei denn, es muss aus Code erfolgen, indem z. B. ein scriptElement erstellt, seine asyncEigenschaft auf festgelegt falseund es in das Dokument eingefügt wird?
John Dvorak
2
@ JanDvorak, aber wie bestätigen Sie, dass die Bibliothek nicht geändert wurde?
Peter Taylor
script/javascript? Du meinst text/javascript.
Nyuszika7h

Antworten:

6

844 Zeichen

K=new XMLHttpRequest;K.open("get","http://localhost:8000",O=j=n=q=0);K.send();m=K.response;l=m.length;k=l+1|63;W=[o=Math.pow];for(H=[R=o(i=2,32)];i<313+l;i++)for(W[i]||(K[q]=o(i,1/3)*R|0,H[q++]=o(i,.5)*R|0,I=2);W[i*I++]=199>I;)o[n>>2]|=(n^l?m.charCodeAt(n):128)<<24-n++%4*8;for(o[k>>2]=8*l;j<=k;H[I-7]+=a=t+T|0)i=j++&63||(a=H[0],b=H[1],c=H[2],d=H[3],e=H[4],f=H[5],g=H[6],h=H[7],0),y=W[i-15],x=W[i-2],t=h+(e<<26^e>>>6^e<<21^e>>>11^e<<7^e>>>25)+(e&f^~e&g)+K[i]+(W[i]=16>i?o[O++]:(y<<25^y>>>7^y<<14^y>>>18^y>>>3)+W[i-7]+(x<<15^x>>>17^x<<13^x>>>19^x>>>10)+W[i-16]),T=(a<<30^a>>>2^a<<19^a>>>13^a<<10^a>>>22)+(a&b^a&c^b&c),H[I=i-63|7]+=h=g,H[I-1]+=g=f,H[I-2]+=f=e,H[I-3]+=e=d+t|0,H[I-4]+=d=c,H[I-5]+=c=b,H[I-6]+=b=a;1581216710^H[0]|111684968^H[1]|4014012921^H[2]|1079115982^H[3]|615382426^H[4]|1099547324^H[5]|956388165^H[6]|4042147540^H[7]||eval(m)

Die URL- und Hash-Werte sind fest codiert. Der Hash ist als dezimales Doppelwort codiert, die Werte in meinem Code entsprechen dem Skript im Beispiel-Python-Server.

Ich habe mich auch nicht um die URL-Codierung gekümmert, aber sie funktioniert, wenn Sie tippen javascript: manuell in die URL-Leiste und dann den Code (und auch in die Konsole) einfügen.

Die Implementierung ist nicht konform, sollte jedoch für Dateien mit einer Größe von weniger als 512 MB funktionieren.

Kopieren
quelle