13

Estoy tratando de descubrir cómo ofuscar los identificadores de mis registros en rieles.¿Cómo puedo oscurecer los identificadores de mis registros en los rieles?

Por ejemplo, una ruta de acceso típica puede parecerse a http://domain/records/1, por lo que es bastante fácil para las personas deducir cuánto tráfico recibe el sitio si solo crean un nuevo registro.

Una solución que he usado es la de hash con una sal, pero como no estoy seguro de si esa función es biyectiva, termino almacenándola en otra columna en mi base de datos y comprobo la exclusividad.

Otra opción en la que estaba pensando fue generar un hash aleatorio y almacenarlo como otra columna. Si no es único ... solo genera otro.

¿Cuál es la mejor manera de hacerlo?

Respuesta

24

Puede usar la biblioteca integrada OpenSSL para encriptar y descifrar sus identificadores, de esa manera solo necesitaría sobreescribir to_param en sus modelos. También necesitará usar Base64 para convertir los datos cifrados en texto sin formato. Me gustaría seguir esto en un módulo para que pueda ser reutilizado:

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 

Así que ahora sus modelos tendría que ser algo como esto:

class MyModel < ActiveRecord::Base 
    include Obfuscate 

    def to_param 
    encrypt id 
    end 
end 

Luego, en su controlador cuando se necesita para encontrar un registro por el ID de cifrado, se usaría algo como esto:

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

Si usted está buscando para cifrar/descifrar los identificadores sin almacenándolos en la base de datos, esta es probablemente la forma más fácil de hacerlo.

+1

Esto es increíble :) ¿Hay alguna forma de deshacerse del "==" adicional al final de la cadena cifrada? – Cyrus

+0

Eso es parte del cifrado Base64, por lo que es necesario para descifrar la identificación. Podrías eliminarlo en el método 'encrypt' y agregarlo nuevamente en el método' decrypt', pero podría ser más propenso a error (ya que no estoy seguro de si Base64 * siempre * incluye ''==' al final de la cuerda). – siannopollo

+2

Base64 rellena con '=' si el número de bits codificados no coincide exactamente. Habrá cero, uno o dos de ellos dependiendo de la duración de la entrada. – tadman

3

Es muy fácil generar identificadores aleatorios únicos para sus registros, ya sea utilizando un generador de cadenas aleatorias o una simple llamada a Digest::SHA1.hexdigest que produce resultados aleatorios y criptográficos únicos.

Por ejemplo, puede crear una columna secundaria llamada ident o unique_id que almacena sus identificadores públicos. A continuación, puede sobre-escritura to_param utilizar esto en su lugar:

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 

Teóricamente existe la posibilidad de colisión en SHA1 pero las probabilidades son tan astronómicamente baja es más susceptible de tener un accidente de software debido a un error de memoria o mal funcionamiento del hardware. Puede probar esto para ver si satisface sus necesidades al generar unas pocas mil millones de identidades para ver si alguna vez colisionan, lo que no deberían hacer. Un número aleatorio de 256 bits debería proporcionar una cantidad suficiente de datos para que el algoritmo SHA1 se mastique.

+1

Me alegra que las grandes mentes piensen igual. Eso es básicamente lo que estaba tratando de hacer. – Cyrus

+0

Creo que OP también quería descifrar el resultado ofuscado sin depender de una búsqueda. –

5

En lugar de identificadores numéricos, utilice algún tipo de url amigable o babosa humana legible. Hay lots of tools para elegir en este departamento. No solo son más amigables para sus usuarios, sino que las babosas bien elegidas pueden dar una buena ventaja con los motores de búsqueda.

4

Aquí es una joya que lo mantiene numérico, no requiere migraciones de bases de datos, y no hay cambios de enrutamiento: https://github.com/namick/obfuscate_id


he encontrado que esta joya no funciona en concierto con otras gemas, especialmente paper_trail. Esto se debe a la forma en que reemplaza el método find, y paper_trail hace que se llame a find con la identificación del registro real.

Así que he estado usando la funcionalidad "scatter_swap" de la gema, pero no el resto. Aquí está el modelo:

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 

Y en el controlador:

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 

Como alternativa a la redirección que tiene lugar allí, simplemente podría incluir una URL canónica en la página. La redirección tiene el error de ignorar los parámetros de consulta en la URL. Esto no fue un problema para mi proyecto, ya que no tenía ninguno. Pero una URL canónica sería mejor.

Cuestiones relacionadas