13

Sto cercando di capire come nascondere gli ID dei miei record su binari.Come posso nascondere gli ID dei miei record nelle rotaie?

Ad esempio: un percorso tipico potrebbe essere simile a http://domain/records/1, quindi è abbastanza facile per le persone dedurre la quantità di traffico che il sito riceve se crea solo un nuovo record.

Una soluzione che ho usato è hash l'id con un salt, ma poiché non sono sicuro se tale funzione è bijective, finisco per archiviarlo in un'altra colonna nel mio database e ricontrollare per univocità.

Un'altra opzione a cui stavo pensando era generare un hash casuale e archiviarlo come un'altra colonna. Se non è unico ... basta generarne un altro.

Qual è il modo migliore per farlo?

risposta

24

È possibile utilizzare la libreria OpenSSL integrata per crittografare e decrittografare i propri identificatori, in questo modo è sufficiente sovrascrivere to_param sui modelli. Dovrai inoltre utilizzare Base64 per convertire i dati crittografati in testo normale. Vorrei restare presente in un modulo in modo che possa essere riutilizzato:

require 'openssl' 
require 'base64' 

module Obfuscate 
    def self.included(base) 
    base.extend self 
    end 

    def cipher 
    OpenSSL::Cipher::Cipher.new('aes-256-cbc') 
    end 

    def cipher_key 
    'blah!' 
    end 

    def decrypt(value) 
    c = cipher.decrypt 
    c.key = Digest::SHA256.digest(cipher_key) 
    c.update(Base64.decode64(value.to_s)) + c.final 
    end 

    def encrypt(value) 
    c = cipher.encrypt 
    c.key = Digest::SHA256.digest(cipher_key) 
    Base64.encode64(c.update(value.to_s) + c.final) 
    end 
end 

Così ora i vostri modelli avrebbe bisogno di essere simile a questa:

class MyModel < ActiveRecord::Base 
    include Obfuscate 

    def to_param 
    encrypt id 
    end 
end 

Poi nel controller quando si ha bisogno di trovare un record dalla id crittografato, si può usare qualcosa di simile:

MyModel.find MyModel.decrypt(params[:id]) 

Se stai cercando di cifrare/decifrare ids senza memorizzandoli nel database, questo è probabilmente il modo più semplice per andare.

+1

Questo è fantastico :) C'è un modo per eliminare l'extra "==" alla fine della stringa crittografata? – Cyrus

+0

Questo fa parte della crittografia Base64, quindi è necessario decrittografarlo. Puoi rimuoverlo nel metodo 'encrypt' e aggiungerlo di nuovo nel metodo' decrypt', ma potrebbe essere più incline all'errore (dato che non sono sicuro che Base64 * always * includa '==' alla fine della stringa). – siannopollo

+2

Base64 esce con '=' se il numero di bit codificati non corrisponde esattamente. Ci saranno zero, uno o due di essi a seconda della lunghezza dell'input. – tadman

3

È piuttosto facile generare identificatori casuali univoci per i record utilizzando un generatore di stringhe randomizzato o una semplice chiamata a Digest::SHA1.hexdigest che produce risultati univoci ragionevolmente casuali e crittografici.

Ad esempio, è possibile creare una colonna secondaria denominata ident o unique_id che memorizza i propri identificatori pubblici. È quindi possibile over-scrittura to_param di utilizzare questo, invece:

class MyModel < ActiveRecord::Base 
    before_create :assign_ident 

    def self.from_param(ident) 
    find_by_ident(ident) 
    end 

    def to_param 
    self.ident 
    end 

protected 
    def assign_ident 
    self.ident = Digest::SHA1.hexdigest(SecureRandom.random_number(1<<256).to_s) 
    end 
end 

In teoria c'è la possibilità di collisione SHA1 ma le probabilità sono così astronomicamente bassa siete più suscettibili di avere un crash del software a causa di un errore di memoria o malfunzionamento dell'hardware. Puoi testarlo per vedere se soddisfa le tue esigenze generando qualche miliardo di identità per vedere se si scontrano, cosa che non dovrebbero. Un numero casuale a 256 bit dovrebbe fornire una quantità sufficiente di dati per l'algoritmo SHA1 da masticare.

+1

Sono contento che le grandi menti la pensino allo stesso modo. Questo è fondamentalmente quello che stavo cercando di fare. – Cyrus

+0

Penso che OP voleva essere in grado di decifrare il risultato offuscato senza fare affidamento su una ricerca. –

5

Invece di ID numerici, utilizzare un tipo di URL amichevole o di slug leggibile dall'uomo. Ci sono lots of tools da scegliere in questo dipartimento. Non solo sono più amichevoli per i tuoi utenti, ma gli slug ben scelti possono dare un buon vantaggio ai motori di ricerca.

4

Ecco una gemma che mantiene numerico, non richiede migrazioni di database, e nessun cambiamento di routing: https://github.com/namick/obfuscate_id


ho trovato che questo gioiello non funziona in concerto con alcune altre gemme, in particolare paper_trail. Ciò è dovuto al modo in cui sostituisce il metodo find e paper_trail fa sì che find venga chiamato con l'ID record effettivo.

Quindi ho usato la funzionalità "scatter_swap" della gemma, ma non il resto. Ecco il modello:

require 'obfuscate_id/scatter_swap' 

class Page < ActiveRecord::Base 
    # This is a random number that, if changed, will invalidate all existing URLs. Don't change it! 
    @@obfuscate_spin = # random number here, which is essentially the encryption key 

    ## 
    # Generate URL parameter to be used in the URL as the "id" 
    def to_param 
    # Use the obfuscate_id gem's class to "spin" the id into something obfuscated 
    spun_id = ScatterSwap.hash(self.id, @@obfuscate_spin) 

    # Throw any additional attributes in here that are to be included in the URL. 
    "#{spun_id} #{name}".parameterize 
    end 

    def self.find_by_slug!(slug) 
    spun_id = slug[/^[0-9]+/] 
    begin 
     find_by_id! ScatterSwap.reverse_hash(spun_id, @@obfuscate_spin) 
    rescue ActiveRecord::RecordNotFound => e 
     raise ActiveRecord::RecordNotFound, "Couldn't find matching Page." 
    end 
    end 
end 

E nel controller:

class PagesController < InheritedResources::Base 
    # Find the page using its URL slug 
    before_filter :find_page, except: [:index, :create, :new] 

    def find_page 
    @page = Page.find_by_slug! params[:id] 

    # If the URL doesn't match exactly, and this is a GET. 
    # We'll redirect to the new, correct URL, but if this is a non-GET, let's let them finish their request instead. 
    if params[:id] != @page.to_param && request.get? 
     redirect_to url_for({ id: @page.to_param }), status: 301 
    end 
    end 
end 

In alternativa il reindirizzamento che avviene lì, si può semplicemente inserire un URL canonico nella pagina. Il reindirizzamento ha il bug di ignorare qualsiasi parametro di query nell'URL. Questo non era un problema per il mio progetto, perché non ne avevo. Ma un URL canonico sarebbe meglio.

Problemi correlati