The Rails API/Website for
Ruby CSS CoffeeScript JavaScript
Clone or download
Pull request Compare This branch is 18 commits ahead, 3 commits behind Bren2010:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
bundle.pem - Rails API/Website

This is the official repository of, a secure cloud storage site for text snippets. Onions is perfect for passwords, personal information, or anything else that can be condensed down to characters. Onions is not secure file storage - with no plans to become that in the future (hosting costs, etc). This repository houses the Rails app used to power the API for client applications and the website.

Onion - An Onion is just a blob of text, that contains a title and info.

Security Model

Security is absolutely the most important feature of Onions. It employs a few different implementations and philosophies to obfuscate your data in totality. To begin with, every connection made to the API and using the website is made over an SSL connection. You can verify this by checking out the address bar for the website.


No emails or personal information is ever received when you create an account - just a username to identify it and a password. The downside of this is that your password can never be reset, or changed, or retrieved if you forget it. However, the upside of this model is that your account is practically anonymous assuming you don't choose a username that ties to you somehow (like firstnamelastname or your email). Beyond that, another security precaution is taken into account where your username is salted via a propietary salt that lives only on the server - something not in this repository. After salting, it is hashed using SHA-256, and then saved in the database. This will be regarded as HashedUser from here on out.

Your password is salted and hashed via the encryption library, BCrypt. The standard Ruby bcrypt library is employed in the app to handle this implementation.

A second salt is created using BCrypt for the next part of the app, encrypting/decrypting Onions. This leaves the Account object looking like this:

// Account
+ HashedUser = SHA-256(username + SERVER_SALT)
+ HashedPass = BCrypt(password, 10 rounds)
+ Salt =


No Onions are ever stored in plaintext on the server either. They are encrypted via AES-256 encryption, using the CBC cipher method, before ever being sent to the server to be saved in the database. Another modern cryptography implementation is employed to generate your private AES Key, PBKDF2. PBKDF2 is used with 20,000 rounds of key-stretching to generate your AES Key in both the client and website applications, and the specific algorithm is HMAC-SHA1. Doing this makes it harder, or more time intensive, to brute-force your encryption key, and provides a level of security that meets contemporary standards.

An Onion has a title and info as its properties. The title is just an 80 character or less blurb about what is contained inside. The info is 800 characters or less and is the meat of what you are saving. Both of these fields are encrypted before being sent to the server - resulting in a data structure that looks like so:

// AES Key
+ AES_Key = PBKDF2(HMAC_SHA1, (plaintext username + plaintext password), Account.Salt, 20000 iterations)

// Onion
+ HashedTitle = AES_Encrypt(title, AES_Key)
+ HashedInfo = AES_Encrypt(info, AES_Key)
+ HashedUser = Account.HashedUser


The client applications are given Session objects after logging in, allowing them to retrieve, manipulate, create or delete Onions on the server. The Session object contains a Key, HashedUser and CreationDate properties used to guarantee authenticity and allowing the client to make changes. The Key property is a random GUID given at runtime that is matched to an Account that just logged in. The CreationDate is used to maintain an hour timeline on making changes to the server without having to log back in again.

The Session objects also roll after every action. Basically this means that on login you get a Session object - then after you create a new Onion, that Session object is deleted and a new one is given to the client. If you edit an Onion and save it, a new Session object is given to you. If you sit for an hour after logging in, and then try to manipulate the data, the client will log you out and return you to the login screen. The data model for this looks like so:

// Session
+ Key = 32-bit GUID
+ HashedUser = Account.HashedUser
+ Created_At = DateTime object

Security Implementations

Most of the security implementations happen in the data models account.rb, onion.rb, session.rb. You can see the considerations made above in the code below:

// account.rb

require 'bcrypt'
require 'openssl'

class Account < ActiveRecord::Base
  attr_accessible :HashedUser, :HashedPass, :Salt

  # Authenticate User/Pass
  def self.pass_is_good(pass,username)
  	@accounts = Account.where(:HashedUser => username)
  	@user = @accounts[0]
  	hashed_pass = @user[:HashedPass]
  	hashed_pass == pass

  # Create new Hashed Password from a plaintext input
  def self.new_hashed_pass(pass)
  	hashed_pass = BCrypt::Password.create pass
  	return hashed_pass.to_s

  # Determine if an acount exists in the Accounts Table
  def self.account_exists(username)
  	user = Account.where(:HashedUser => username)

  # Generate a random Salt
  def self.generate_salt
  	return BCrypt::Engine.generate_salt.to_s

  # Hash a plaintext username
  def self.hashed_user(username)
    keyed_user = username + ENV['ONIONS_AES']

  # Generate an AES key using plaintext pass, user & Account.Salt
  def self.aes_key(username,pass,salt)
    return OpenSSL::PKCS5.pbkdf2_hmac_sha1((pass+username), salt, 20000, 16).unpack('H*')[0]
// onion.rb

require 'base64'
require 'openssl'

class Onion < ActiveRecord::Base
  attr_accessible :HashedInfo, :HashedTitle, :HashedUser, :Title_Iv, :Info_Iv, :Title_AuthTag, :Info_AuthTag

  # Take Onions from DB and decrypt them all after a User logs in
  def self.decrypted_onions_with_key(onions,key)
  	if onions && key
      betaProblems = false
  		onions.each do |o|
        hash = Onion.decrypted_onion(key, o)
  			if hash[:BetaProblems]
          betaProblems = true

  	return {:Onions => onions, :BetaProblems => betaProblems}

  # Encrypt data
  def self.aes256_encrypt(key, data)
    key = Digest::SHA256.digest(key) if(key.kind_of?(String) && 32 != key.bytesize)
    aes =, :CBC)
    aes.key = key
    new_iv = aes.random_iv
    encrypted_data = aes.update(data) +
    auth_tag = Digest::SHA256.digest(ENV['AUTH_TAG'] + encrypted_data)
    # return encrypted data, the iv, and the authentication info
    return {:EncryptedData => Base64.encode64(encrypted_data), :Iv => Base64.encode64(new_iv), :AuthTag => Base64.encode64(auth_tag)}

  # Decrypt data
  def self.aes256_decrypt(key, data, iv, auth_tag)
    if Digest::SHA256.digest(ENV['AUTH_TAG'] + data) == auth_tag
      key = Digest::SHA256.digest(key) if(key.kind_of?(String) && 32 != key.bytesize)
      aes =, :CBC)
      aes.key = key
      aes.iv = iv
      return {:Data => aes.update(data) +, :BetaProblems => false}
      # Message didn't authenticate
      return {:Data => data, :BetaProblems => true}

  def self.create_new_onion(key,title,info,user)
    @e_title_hash = Onion.aes256_encrypt(key,(title.length > 0 ? title : ' '))
    @e_info_hash = Onion.aes256_encrypt(key,(info.length > 0 ? info : ' '))
    @new_onion = Onion.create(:HashedUser => user, :HashedTitle => @e_title_hash[:EncryptedData], :Title_Iv => @e_title_hash[:Iv], :Title_AuthTag => @e_title_hash[:AuthTag], :HashedInfo => @e_info_hash[:EncryptedData], :Info_Iv => @e_info_hash[:Iv], :Info_AuthTag => @e_info_hash[:AuthTag])

  def edit_onion_with_new_data(key,new_title,new_info)
    @e_title_hash = Onion.aes256_encrypt(key,(new_title.length > 0 ? new_title : ' '))
    @e_info_hash = Onion.aes256_encrypt(key,(new_info.length > 0 ? new_info : ' '))
    self.HashedTitle = @e_title_hash[:EncryptedData]
    self.Title_Iv = @e_title_hash[:Iv]
    self.Title_AuthTag = @e_title_hash[:AuthTag]
    self.HashedInfo = @e_info_hash[:EncryptedData]
    self.Info_Iv = @e_info_hash[:Iv]
    self.Info_AuthTag = @e_info_hash[:AuthTag]

  def self.decrypted_onion(key,onion)
    hashedTitle = Onion.aes256_decrypt(key,Base64.decode64(onion.HashedTitle),Base64.decode64(onion.Title_Iv),Base64.decode64(onion.Title_AuthTag))
    hashedInfo = Onion.aes256_decrypt(key,Base64.decode64(onion.HashedInfo),Base64.decode64(onion.Info_Iv),Base64.decode64(onion.Info_AuthTag))
    onion.HashedTitle = hashedTitle[:Data]
    onion.HashedInfo = hashedInfo[:Data]
    betaProblems = hashedTitle[:BetaProblems] || hashedInfo[:BetaProblems]
    return {:BetaProblems => betaProblems}

// session.rb

require 'securerandom'

class Session < ActiveRecord::Base
  attr_accessible :HashedUser, :Key

  def self.new_session(user_hash)
  	session_key = SecureRandom.uuid
  	Session.where(:HashedUser => user_hash).destroy_all
  	s = Session.create(:HashedUser => user_hash, :Key => session_key.to_s)
    if > 2000000000
  	return session_key.to_s

  def self.user_hash_for_session(session_key)
  	@sessions = Session.where(:Key => session_key)
  	@session = @sessions[0]
    if @session
      if @session.created_at + 1.hours >
         return @session.HashedUser
         return nil

    return nil

  def self.reset_sessions
    # Reset Sessions table if Primary Key goes over 2 billion
    s = Session.find_by_sql('ALTER SEQUENCE sessions_id_seq RESTART WITH 1')


Coming Soon

  • iPhone app
  • iPad app
  • Mac OS X app
  • Android app


The Edited MIT License (MIT)

Copyright (c) Benjamin Gordon 2013

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, distribute, copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

  • You cannot sell the software, rebranded as your own or in its current form.
  • You must link back to this repository if you use any bit of the propietary parts of this software.