Spend¶
Usage¶
First use¶
This page is made for first use of the hardware
You will need to tweak a few things on the SD card you received with the device : You have to tell the device for who it works
Warning
Spend is using symetric and asymetric encryption, using veracrypt containers and PGP-protected messages. If you want use these features, you should learn how it works. Some links about that :
A GPG keyring have been created for you, and Spend is setup to use it as default recipient.
You can find it on the SD card, in the folders : /home/spend/KEYRING-TO-REMOVE
.
It contains all the keyring file plus one which is the keyring’s password.
Note
You can use the autogenerated keyring if you want, but for more security you are advised to use you own instead. see Importing a new public key
A randomly-generated password used internally by the device is present in `/home/spend/ROOT_PASSWORD_TO_SAVE_AND_DELETE.txt`
If you want to play a bit with the device you are advised to keep it, but that’s not such a matter as OS is not encrypted
Now that Spend knows the public key that it should use, you can insert the Sd card at the rear of the device and start it (but it’s better to continue reading the documentation before)
Workflow¶
In this chapter, spend working process is succinctly described : how it look for devices, target files on it, how it chooses what to do, and which kind of encryption it uses
Devices¶
Once Spend is on and running, it will triggers a device research. If no seemingly interesting device is found, it will start monitoring devices plug.
Whenever a device is found, a timer in Configuration is set to let the user the possibility to plug another device.
Warning
Only mounted devices are processed. That means that if the device is somehow in a abnormal state, it will not be considered.
Supported devices¶
Due to its low electrical power, Spend should not be use with external hard drive that have no external power sources. Actually, it has only be tested with
- various cheap and wide-spread USB pens
- SD cards
- µSD card
Targets¶
Once a device is detected, it is inspected to see how much space is available, and if interesting files are present.
By default, target files are images and videos only, but this can be changed in the Configuration options
Warning
If you ask Spend to proceed all file types, it can quickly get stuck if the device where you want to output already contains files
If encryption is off, Spend will copy the files in a folder named output
The files present in this folder are then ignored by the scans. This allows Spend to be used many with the same output device
Decision¶
Once all data about device is gathered, Spend have to choose which device will be used as input and which one as output.
Warning
Spend does not support use of more than two devices at the same time
Note
If only one device is present, the only thing that can be done is encrypting the files and storing them on the same device (other things are meaningless)
Decision is taken according to this heuristic :
- If device A have interesting file and device B not, copy from A to B
- If device A and B have interesting files :
- If one device is recognized as an SD card, copy from the SD to the other device
- Else fail
Note
It can be annoying that the output device needs to be free of files that can be considered as targets.
However you can place them in a folder named output
on the root of your removable drive.
They will be excluded from scan
Encryption¶
Spend offers encryption functionality, with veracrypt and GPG.
Warning
Spend is using symetric and asymetric encryption, using veracrypt containers and PGP-protected messages. If you want use these features, you should learn how it works. Some links about that :
Encryption works this way :
- A random name for the run is chosen. Let’s call it
RunA
- A random password (
KeyA
) is created. It is stored PGP-encrypted on the output device, under the nameRunA.key
- A veracrypt encrypted volume named
RunA.files
, which can be open withKeyA
, is created on the output device - Targets files are stored in the encrypted volume
So, to decrypt the files :
- Decrypt the file
xxx.key
with PGP, using your favorite software. - Open the veracrypt volume, called
xxx.files
. The password is inside the file you just opened via PGP
Importing a new public key¶
Spend have an automatic update system which lets you use your own GPG keyring to receive the files.
You just have to export your public key (cf. documentation of the software you uses), and store it in Spend’s SD card at this place : /home/spend/update/key.gpg
Update¶
As some software updates will probably be supplied, Spend offer a way to update easily.
You just have to put the supplied file in the SD cards, in the folder /home/spend/update
; and start Spend.
Information about update will be displayed on the screen
Note
additional instructions may be given altogether with the update files
Configuration¶
Some options can be changed in a configuration file, located in /home/spend/.spend/prod.ini
Key | Type | Usage |
---|---|---|
display | boolean | Toggle screen activation |
led | boolean | Toggle led activation |
button | boolean | Toggle buttons activation |
home_dir | string | Home directory. Used by other options |
shred_state_file | string | File that contains led state bit |
crypt_state_file | string | File that contains crypt state bit |
log_stacktrace | boolean | Log trace in a file on error in the main process |
wait_for_another_device_seconds | int | Time slept between first device detection and actual run |
seeked_files | string | Targets mime types split by | . ex: image|video |
… | … | … |
recipient | string | Fingerprint of file recipient’s GPG public key |
Troubleshooting¶
Common problems¶
Error codes¶
Errors displayed on the screen contains a code as last line. You can check this code meaning in this table :
Message | Code | Meaning |
---|---|---|
Update error | UP00 | Generic update error |
Update error | UP10 | Error on GPG key update |
PGP encryption error | ENC50 | Error while encrypting volume key with PGP |
Encryption error | ENC20 | Error while creating or encrypting Veracrypt volume |
No devices found… | SCAN00 | Devices scan returned no usable thing |
Add other device or choose encryption | CHOS10 | Only one device found, no encryption ask. Ask for something different |
No targets found | CHOS20 | No targets files have been found in devices |
Not enought space | INSP60 | No device with enought space found |
Error in pgp config | PGP10 | PGP recipient not set or badly set. cf see Importing a new public key |
More than 2 devices not supported | SCAN2 | More than two device detected. This is not supported |
Target files on both sides | CHOS2 | Two devices plugged, with target files on both. Can’t choose what to do. |
Technical documentation¶
spend package¶
Subpackages¶
spend.display package¶
Submodules¶
spend.display.Message module¶
-
class
spend.display.Message.
Message
(content=None, level=30, duration=None, template=2, is_last_one=False)¶ Bases:
object
Displayed message representation. Comparable, Hashable, Human-readable
-
LEVEL_CRITICAL
= 0¶
-
LEVEL_DEBUG
= 40¶
-
LEVEL_ERROR
= 10¶
-
LEVEL_INFO
= 30¶
-
LEVEL_WARNING
= 20¶
-
TEMPLATE_TYPES
= [1, 2, 3]¶
-
TEMPLATE_TYPE_ERROR
= 3¶
-
TEMPLATE_TYPE_NORMAL
= 2¶
-
TEMPLATE_TYPE_SPLASH
= 1¶
-
can_overtake
(message)¶ Check if message in argument can overtake A message can overtake others with lower level, or with same level and no duration
Parameters: message (Message) Returns: Return type: boolean can_overtake
-
spend.display.MessageHandler module¶
spend.display.QueueManager module¶
-
class
spend.display.QueueManager.
PreviousItemsList
¶ Bases:
list
Old thing ? List that can have only one Message without duration On insert, previous elem is discarded
-
class
spend.display.QueueManager.
QueueManager
(queue, message_handler)¶ Bases:
threading.Thread
This module handles Message bus management. It allows Message prioritization and Temporary messages.
-
CHECK_FREQUENCY
= 1¶
-
current_item
= None¶
-
display
(message: spend.display.Message.Message)¶ Store argument as currently displayed message and pass it to the Handler
Parameters: message (Message)
-
external_queue
= None¶
-
kill
()¶ kills instance without waiting
-
message_handler
= None¶
-
next
(message: spend.display.Message.Message)¶ function called by a Timer when it wakes up. Trigger update
Parameters: message (Message)
-
poll
()¶ Get a new item from the Queue and process it
-
previous_items
= []¶
-
process_message
(raw_message: Union[spend.display.Message.Message, logging.LogRecord])¶ Check if message can be displayed considering current item. Start Timer if needed and ask for display
Parameters: raw_message (Union[Message, LogRecord]) Raises: StopListeningException
– Used to stop the Manager. Triggered when receiving Message(is_last_one=True)
-
process_previous_items
(new_item=Message: [Lvl.40] )¶ Check if previous messages can be displayed considering new Message. If last previous item can overtake new one and nothing is displayed, of if it can overtake both ; it can be used
-
run
()¶ Loop until StopListeningException is received
-
stop
()¶ Wait for Messages with duration to complete and stop instance
-
timers
= {}¶
-
-
exception
spend.display.QueueManager.
StopListeningException
¶ Bases:
BaseException
spend.display.init module¶
Functions managing global oled communication environment : PriorityQueue joining QueueManager and a logging.Logger
-
spend.display.init.
kill_display_management
(display_props)¶ Stop display management with kill flag
Parameters: display_props
-
spend.display.init.
start_display_management
()¶ - Create PriorityQueue
- Create logging Handler
- Create QueueManager thread reading from the PriorityQueue
Returns: {‘queue_manager’ Return type: QueueManager, ‘message_queue’: PriorityQueue, ‘handler’: logging.Handler}
-
spend.display.init.
teardown_display_management
(display_props, kill=False)¶ Stops disaplys management. Join threads.
Parameters: - display_props ({‘queue_manager’: QueueManager, ‘message_queue’: PriorityQueue, ‘handler’: logging.Handler})
- kill (bool)
spend.display.logging module¶
This file hold classes for a custom logger, preparing LogRecord containing a Message to be push in a PriorityQueue Overrides in order to
- filter log record not meant to be displayed
- Put only Message instances in the queue
-
class
spend.display.logging.
DisplayFilter
(name='')¶ Bases:
logging.Filter
-
filter
(r)¶ Filter non-Message LogRecords
-
-
class
spend.display.logging.
DisplayFormatter
(fmt=None, datefmt=None, style='%')¶ Bases:
logging.Formatter
-
format
(record)¶ Does nothing
-
-
class
spend.display.logging.
DisplayQueueHandler
(queue)¶ Bases:
logging.handlers.QueueHandler
-
prepare
(record)¶ Return actual content
-
-
spend.display.logging.
create_display_handler
(queue: queue.PriorityQueue)¶ Bootstrap handler and registers it
Parameters: queue (PriorityQueue)
Module contents¶
This module abstracts communication from one or many scripts to an OLED screen connected via GPIO It receive Message from a PriorityQueue and display them using a MessageHandler It can be plugged to the logging module for convenient use
Submodules¶
spend.Device module¶
spend.Spend module¶
spend.Volume module¶
spend.exceptions module¶
-
exception
spend.exceptions.
AsymetricEncryptionException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
PGP file encryption returns something else than OK
-
exception
spend.exceptions.
DeviceNotMountedException
¶ Bases:
BaseException
-
exception
spend.exceptions.
DeviceNotWritableException
¶ Bases:
BaseException
-
exception
spend.exceptions.
DisplayableException
(*args, **kwargs)¶ Bases:
Exception
Base class for Exceptions which could be displayed on the OLED display
-
get_duration
()¶
-
-
exception
spend.exceptions.
GhostMountpointException
¶ Bases:
BaseException
-
exception
spend.exceptions.
MoreThanTwoDevicesException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
More than 2 devices found
-
exception
spend.exceptions.
NoDeviceFoundException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
No (interesting) device was found by DeviceScanner
-
exception
spend.exceptions.
NoTargetsFoundException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
One or more devices have been found, but DeviceInspector did not returned any file to be copied
-
exception
spend.exceptions.
NotEnoughtSpaceException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
No device have enough space to store files selected by DeviceInspector
-
exception
spend.exceptions.
SameDeviceAndNoEncryptionException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
Input and output have been set to the same device, and no encryption have been chosen. It fails as it does not makes sense to duplicate files
-
exception
spend.exceptions.
SymetricEncryptionException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
Error on volume creation
-
exception
spend.exceptions.
TargetOnBothSidesException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
Targets found in both devices
-
exception
spend.exceptions.
UnknownRecipientException
(*args, **kwargs)¶ Bases:
spend.exceptions.DisplayableException
PGP recipient specified in config is not present in PGP keyring
-
exception
spend.exceptions.
UpdateException
(*args, **kwargs)¶
spend.helpers module¶
-
spend.helpers.
copy_files
(files, to)¶
-
spend.helpers.
debounce
(s)¶ Decorator ensures function can only be called once every s seconds.
-
spend.helpers.
get_child_processes
(parent_id)¶
-
spend.helpers.
get_config
(filename='prod.ini')¶ Return ConfigObj :Parameters: filename (str Config filename (e.g test.ini))
Returns: Return type: ConfigObj
-
spend.helpers.
get_random_str
(size)¶ Return x chars belonging to letters and digits.
Parameters: size (int Len of the returned string)
-
spend.helpers.
get_resource_requirement
() → pkg_resources.Requirement¶
-
spend.helpers.
read_file_as_bool
(filepath)¶
-
spend.helpers.
read_file_content
(filepath)¶ Lock the file and returns its content :Parameters: filepath (str File path to read)
Returns: Return type: str Raises: IOError: – Lock cannot be obtained
-
class
spend.helpers.
safeutil
¶ Bases:
object
This is a script designed to be “safe” drop-in replacements for the shutil move() and copyfile() functions. These functions are safe because they should never overwrite an existing file. In particular, if you try to move/copy to dst and there’s already a file at dst, these functions will attempt to copy to a slightly different (but free) filename, to avoid accidental data loss. More background here: http://alexwlchan.net/2015/06/safer-file-copying/
-
static
_increment_filename
(filename, marker='-')¶ Returns a generator that yields filenames with a counter. This counter is placed before the file extension, and incremented with every iteration. For example: f1 = increment_filename(“myimage.jpeg”) f1.next() # myimage-1.jpeg f1.next() # myimage-2.jpeg f1.next() # myimage-3.jpeg If the filename already contains a counter, then the existing counter is incremented on every iteration, rather than starting from 1. For example: f2 = increment_filename(“myfile-3.doc”) f2.next() # myfile-4.doc f2.next() # myfile-5.doc f2.next() # myfile-6.doc The default marker is an underscore, but you can use any string you like: f3 = increment_filename(“mymovie.mp4”, marker=”_”) f3.next() # mymovie_1.mp4 f3.next() # mymovie_2.mp4 f3.next() # mymovie_3.mp4 Since the generator only increments an integer, it is practically unlimited and will never raise a StopIteration exception.
-
static
copyfile
(src, dst)¶ Copies a file from path src to path dst. If a file already exists at dst, it will not be overwritten, but: * If it is the same as the source file, do nothing * If it is different to the source file, pick a new name for the copy that is distinct and unused, then copy the file there.
Returns the path to the copy.
-
static
move
(src, dst)¶ Moves a file from path src to path dst. If a file already exists at dst, it will not be overwritten, but: * If it is the same as the source file, do nothing * If it is different to the source file, pick a new name for the copy that is distinct and unused, then copy the file there.
Returns the path to the new file.
-
static
-
spend.helpers.
shred_file
(filename)¶ Shred designed file
Parameters: filename (str Path of the file to be shreded)
-
spend.helpers.
toggle_state
(_file)¶ Switch a file content from one to zero or inverse Make use of a Lock
Parameters: file (str File path) Returns: Return type: bool New value
-
spend.helpers.
umount_devices
(devices)¶ Unmount devices with pumount
Parameters: devices (List[str] List of strings understood by pumount as a device)
-
spend.helpers.
write_file
(filepath, data)¶ Write data to filepath. No exceptions catched. Parameters: ———– filepath: str File path data: str Data to be written in the file
spend.update module¶
Module contents¶
Copyright (C) 2019 spend
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
Super Portable ENcryption Device
Warning
This project is a prototype. It has some known flaws, and can contain various other bugs. Encryption process have not been audited so DO NOT USE IT IN RISKY SITUATIONS !
Spend is a mobile device which allows you to copy and transfer files among flash storages such as USB key, SD cards… It can also encrypt transfered files, using veracrpyt containers for files and PGP to interact with you

This repository contains the main python package, designed to run on an OrangePi Zero with a Oled screen and 3 push buttons connected via GPIO.
Note
v 0.0 has not been released yet, which means that is code is still under development and may not run directly. Updates soon
Project documentation can be found here at readthedocs Repository there at Gitlab
Contact : spend3000 [at] protonmail.com