// -*- 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);
}
}