hash - Finnes det noen SHA-256 JavaScript-implementasjoner som generelt anses som pålitelige?


Jeg skriver en pålogging for et forum, og trenger å hashe passordet på klientsiden i JavaScript før jeg sender det til serveren. Jeg har problemer med å finne ut hvilken SHA-256-implementering jeg faktisk kan stole på. Jeg hadde forventet at det skulle finnes et slags autoritativt skript som alle brukte, men jeg finner mange forskjellige prosjekter, alle med sine egne implementeringer.

Jeg innser at det å bruke andres kryptografi alltid er et sprang i troen med mindre du selv er kvalifisert til å vurdere det, og at det ikke finnes noen universell definisjon av «pålitelig», men dette virker som noe vanlig og viktig nok til at det burde finnes en slags konsensus om hva man bør bruke. Er jeg bare naiv?

Rediger siden det kommer opp mye i kommentarene: Ja, vi gjør en mer streng hash igjen på serversiden. Hashing på klientsiden er ikke det endelige resultatet som vi lagrer i databasen. Hashing på klientsiden er fordi den menneskelige klienten ber om det. De har ikke gitt en spesifikk grunn til hvorfor, sannsynligvis liker de bare overkill.

Svar #1

På https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest fant jeg dette utdraget som bruker intern js-modul:

async function sha256(message) {
    // encode as UTF-8
    const msgBuffer = new TextEncoder().encode(message);                    

    // hash the message
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);

    // convert ArrayBuffer to Array
    const hashArray = Array.from(new Uint8Array(hashBuffer));

    // convert bytes to hex string                  
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}

Merk at crypto.subtle kun er tilgjengelig på https eller localhost - for eksempel for lokal utvikling med python3 -m http.server må du legge til denne linjen i din /etc/hosts: 0.0.0.0 localhost

Start på nytt - og du kan åpne localhost:8000 med fungerende crypto.subtle.

Svar #2

De fleste moderne nettlesere støtter nå kryptografiske operasjoner direkte. Se https://developer.mozilla.org/en-US/docs/Web/API/Crypto.

Når det er sagt, er klient-side kryptografi vanligvis ikke så god idé som det kan virke.

Det grunnleggende prinsippet er at du (på serversiden) må anta at alt som skjer på klientsiden kan være ondsinnet, og kanskje ikke engang kjører koden din i det hele tatt. Hvis dine kryptografiske operasjoner er ment å beskytte brukerens data mot oppdagelse eller innblanding fra en tredjepart, er det passende kryptolaget å bruke TLS i stedet for å lage ditt eget på klienten.

Å sende sensitiv data over TLS krever ikke ytterligere kryptering under overføringen utover det som TLS gir transparent for å overholde lover eller beste praksis. Hvis du sender sensitiv data, send det over TLS. Fra applikasjonens perspektiv vil dette se ut som ren tekst, men det er helt passende og bare en refleksjon av krypteringens abstraksjonsnivå, ikke sikkerhetsnivået den gir. Bare fordi en sikkerhetsmekanisme føles enkel å bruke, betyr det ikke at beskyttelsen den gir er enkel.

Og ikke si: «Jeg bruker allerede TLS, men jeg gjør det BEDRE ved å også gjøre det på klientsiden.» Å gjøre applikasjonen din mer komplisert med flere sikkerhetsrelaterte komponenter gir ikke mer sikkerhet. I stedet ofrer du ekte langsiktig sikkerhet for å føle at du gjør mer.

Konteksten som er angitt for dette spørsmålet er akkurat det feil stedet å gjøre klient-side kryptografi. Det er en god grunn til at store teknologiselskaper aldri gjør det dette spørsmålet antyder, og de lykkes med å holde unna angripere på nasjonsnivå.

Tilfellet der klient-side nettleserkrypto er passende er når klienten anses som frittstående og brukerens beskyttelse ikke forventes å bli ivaretatt av serveroperatøren. Dette er en usedvanlig sjelden situasjon, men dukker opp i ting som offline nettapplikasjoner.

Svar #3

For de som er interesserte, dette er kode for å lage SHA-256-hash ved bruk av sjcl:

import sjcl from 'sjcl'

const myString = 'Hello'
const myBitArray = sjcl.hash.sha256.hash(myString)
const myHash = sjcl.codec.hex.fromBits(myBitArray)

Svar #4

Forges SHA-256-implementering er rask og pålitelig.

For å kjøre tester på flere SHA-256 JavaScript-implementasjoner, gå til http://brillout.github.io/test-javascript-hash-implementations/.

Resultatene på maskinen min antyder at forge er den raskeste implementeringen og også betydelig raskere enn Stanford Javascript Crypto Library (sjcl) nevnt i det aksepterte svaret.

Forge er 256 KB stor, men å hente ut SHA-256 relatert kode reduserer størrelsen til 4,5 KB, se https://github.com/brillout/forge-sha256

Svar #5

Nei, det er ingen måte å bruke nettleser-JavaScript på for å forbedre passordsikkerheten. Jeg anbefaler sterkt at du leser denne artikkelen. I ditt tilfelle er det største problemet høne-egg-problemet:

Hva er «høne-egg-problemet» med å levere Javascript-kryptografi?

Hvis du ikke stoler på at nettverket leverer et passord, eller, enda verre, ikke stoler på at serveren ikke beholder brukerhemmeligheter, kan du ikke stole på at de leverer sikkerhetskode. Den samme angriperen som sniffet passord eller leste dagbøker før du introduserer kryptografi, kaprer ganske enkelt kryptokoden etter at du gjør det.

[...]

Hvorfor kan jeg ikke bruke TLS/SSL for å levere Javascript-kryptokoden?

Du kan. Det er vanskeligere enn det høres ut som, men du kan trygt overføre Javascript-kryptografi til en nettleser ved å bruke SSL. Problemet er at når du har etablert en sikker kanal med SSL, trenger du ikke lenger Javascript-kryptografi; du har "ekte" kryptografi.

Som fører til dette:

Problemet med å kjøre kryptokode i Javascript er at praktisk talt hvilken som helst funksjon som kryptoen er avhengig av, kan bli overskrevet stille av ethvert innhold brukt til å bygge vertssiden. Kryptosikkerhet kan bli undergravd tidlig i prosessen (ved å generere falske tilfeldige tall, eller ved å tukle med konstanter og parametere brukt av algoritmer), eller senere (ved å føre nøkkelmateriale tilbake til en angriper), eller --- i det mest sannsynlige scenariet --- ved å omgå kryptoen helt.

Det finnes ingen pålitelig måte for noen del av Javascript-kode å verifisere sitt kjøringsmiljø. Javascript-kryptokode kan ikke spørre: "håndterer jeg virkelig en tilfeldig tallgenerator, eller en etterligning av en som er levert av en angriper?" Og det kan absolutt ikke hevde "ingen har lov til å gjøre noe med denne krypthemmeligheten utenom på måter som jeg, forfatteren, godkjenner". Dette er to egenskaper som ofte finnes i andre miljøer som bruker kryptografi, og de er umulige i Javascript.

I utgangspunktet er problemet dette:

  • Dine kunder stoler ikke på serverne dine, så de vil legge til ekstra sikkerhetskode.
  • Den sikkerhetskoden leveres av serverne dine (de de ikke stoler på).

Eller alternativt,

  • Dine kunder stoler ikke på SSL, så de vil at du bruker ekstra sikkerhetskode.
  • Den sikkerhetskoden leveres via SSL.

Merk: SHA-256 er heller ikke egnet for dette, siden det er så lett å bryte usaltede, ikke-itererte passord med brute force. Hvis du likevel bestemmer deg for å gjøre dette, se etter en implementering av bcrypt, scrypt eller PBKDF2.

Svar #6

Jeg fant denne implementeringen veldig enkel å bruke. Har også en sjenerøs BSD-lisens:

jsSHA: https://github.com/Caligatio/jsSHA

Jeg trengte en rask måte å få heksadesimalstreng-representasjonen av en SHA-256-hash på. Det tok bare 3 linjer:

var sha256 = new jsSHA('SHA-256', 'TEXT');
sha256.update(some_string_variable_to_hash);
var hash = sha256.getHash("HEX");

Relaterte artikler