Este es un rompecabezas de código de golf con una aplicación del mundo real. Algunos navegadores actuales, si ingresa una URL que se parece a
data:text/html,<script>alert("hi")</script>
ejecutará el código JavaScript dado. Ahora suponga que tiene una URL similar a (pseudocódigo):
data:text/html,<script>
myPublicKey="12345678";
cryptoLib=download("http://example.com/somecryptolib.js");
if(sha256sum(cryptoLib) == "12345678")
eval(cryptoLib)
</script>
Si imprimió esto en tarjetas de negocios como un código QR , cualquier persona que accediera a esa URL con un navegador apropiado obtendría un cliente de cifrado de clave pública, con su clave pública precargada, sin tener que instalar nada. Debido a la verificación hash, puede estar seguro de que obtuvieron el software criptográfico real, incluso si su ISP se entromete con el tráfico.
Desafortunadamente, la versión real de este pseudocódigo es bastante larga para un código QR. Mi desafío es: ¿qué tan corto puedes hacerlo? Una implementación podría:
- Sea un dato: ... URL que se ejecuta correctamente desde la barra de direcciones de Chrome y Firefox. (Para crear una información válida: URL, tendrá que codificar% como% 25 y quitar nuevas líneas)
- Tener una URL y un hash SHA-256 incrustado, preferiblemente como literales de cadena de texto sin formato cerca del comienzo
- Descargue un archivo desde una URL utilizando XMLHttpRequest (o una API similar). (Tenga en cuenta que el servidor deberá incluir un encabezado Access-Control-Allow-Origin: * para que esto funcione).
- Si esa URL se cargó correctamente y el resultado es un archivo con el hash esperado, evalúelo (). De lo contrario, no haga nada ni muestre un mensaje de error.
- Todas las funciones de JavaScript incorporadas que están presentes tanto en Chrome como en Firefox son juegos justos, pero cargar bibliotecas es imposible.
- Use la menor cantidad de bytes posible
Hice una versión ingenua usando CryptoJS ( versión minificada ):
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>
Que sale del minificador como:
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>
Probado con este servidor Python mínimo:
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()
La porción de JavaScript tiene 4700 bytes, pero puede ser mucho más pequeña. ¿Qué tan pequeño puede ser?
script
elemento, estableciendo su async
propiedad false
e insertándolo en el documento?
script/javascript
? ¿Quiere decir text/javascript
.
eval
y luego realizar una segunda solicitud con la biblioteca de cifrado cargada sería un error? Pasé algún tiempo trabajando en una solución y terminé haciendo exactamente eso, pero luego me di cuenta de que significa confiar en el ISP para que no se meta con la biblioteca de cifrado, lo cual es un problema.