// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.server.project.utils; import com.google.appinventor.server.encryption.EncryptionException; import com.google.appinventor.server.encryption.EncryptionStrategy; import com.google.appinventor.server.encryption.Encryptor; import com.google.appinventor.server.storage.StorageIo; import java.math.BigInteger; /** * Security related helper functions. * * @author markf@google.com (Mark Friedman) */ public class Security { // Radix of the encrypted userID/projectID value private static final int ENCRYPTED_ID_RADIX = Character.MAX_RADIX; // Number of hex-digits for projectID (bits in a long divided by bits in a hex digit) private static final int ID_DIGITS = Long.SIZE / 4; private static final Encryptor encryptor = EncryptionStrategy.WRITE; private Security() { // COV_NF_LINE } // COV_NF_LINE /** * Encrypts a user ID and a project ID so that it can be included in an URL * without leaking information. The encryption key is the same across all * servers so that any server can decrypt a request. * * @param userId user ID * @param projectId project ID * @return an encrypted string safe to include in an URL */ public static String encryptUserAndProjectId(String userId, long projectId) throws EncryptionException { if ((userId == null) || (userId.isEmpty())) { throw new EncryptionException("Trying to encrypt a null userId"); } // We encrypt the projectId as a fixed number of digits, followed by // the arbitrary length userId. String plain = String.format("%1$0" + ID_DIGITS + "x", projectId) + userId; BigInteger bigint = new BigInteger(padBytes(encryptor.encrypt(plain.getBytes()))); return bigint.toString(ENCRYPTED_ID_RADIX); } /** * Decrypt the user ID from an encrypted string generated by * {@link #encryptUserAndProjectId(String, long)}. * * @param idEnc string generated by encryptUserAndProjectId * @return the userId parameter that was originally passed to * encryptUserAndProjectId or null * if the encrypted string was invalid */ public static String decryptUserId(String idEnc) throws EncryptionException { try { // Decrypt, skip the projectId (fixed length) and return the // rest of the decrypted string as the userId BigInteger bigint = new BigInteger(idEnc, ENCRYPTED_ID_RADIX); String decryptedString = new String(encryptor.decrypt(unpadBytes(bigint.toByteArray()))); return decryptedString.substring(ID_DIGITS); } catch (NumberFormatException e) { throw new EncryptionException(e); } } /** * Decrypt the project ID from an encrypted string generated by * {@link #encryptUserAndProjectId(String, long)}. * * @param idEnc string generated by encryptUserAndProjectId * @return the projectId parameter that was originally passed to * encryptUserAndProjectId or * {@link StorageIo#INVALID_PROJECTID} if the encrypted string * was invalid */ public static long decryptProjectId(String idEnc) throws EncryptionException { try { BigInteger bigint = new BigInteger(idEnc, ENCRYPTED_ID_RADIX); String decryptedString = new String(encryptor.decrypt(unpadBytes(bigint.toByteArray()))); // The projectId is the first ID_DIGITS characters of the decrypted string return new BigInteger(decryptedString.substring(0, ID_DIGITS), 16).longValue(); } catch (NumberFormatException e) { throw new EncryptionException(e); } } /* * Prepend a non-zero byte to the beginning of the byte array and return the * new array. */ private static byte[] padBytes(byte[] byteArray) { byte[] paddedBytes = new byte[byteArray.length + 1]; paddedBytes[0] = 0x1; // anything non-zero is good for (int i = 0; i < byteArray.length; i++) { paddedBytes[i+1] = byteArray[i]; } return paddedBytes; } /* * Remove the first byte from the beginning of the byte array and return the * new array. */ private static byte[] unpadBytes(byte[] paddedByteArray) { return java.util.Arrays.copyOfRange(paddedByteArray, 1, paddedByteArray.length); } }