Note that you
probably
should
click the “Back to
root page” link in
Figure 16-19
after a successful deletion—don’t use your browser’s Back button to
return to the message selection list at this point because the delete
has changed the relative numbers of some messages in the list. The
PyMailGUI client program worked around this problem by automatically
updating its in-memory message cache and refreshing the index list on
deletions, but PyMailCGI doesn’t currently have a way to mark older
pages as obsolete.
If your browser reruns server-side scripts as you press your Back
button, you’ll regenerate and hence refresh the list anyhow. If your
browser displays cached pages as you go back, though, you might see the
deleted message still present in the list. Worse, clicking on a view
link in an old selection list page may not bring up the message you
think it should, if it appears in the list after a message that was
deleted.
This is a property of POP email in general, which we have
discussed before in this book: incoming mail simply adds to the mail
list with higher message numbers, but deletions remove mail from
arbitrary locations in the list and hence change message numbers for all
mail following the ones deleted.
As we saw in
Chapter 14
, even
the PyMailGUI client has the potential to get some
message numbers wrong if mail is deleted by another program while the
GUI is
open—
in a second
PyMailGUI instance, for example, or in a simultaneously running
PyMailCGI
server session. This
can also occur if the email server automatically deletes a message
after the mail list has been loaded—for instance, moving it from inbox
to undeliverable on errors.
This is why PyMailGUI went out of its way to detect server inbox
synchronization errors on loads and deletes, usingmailtools
package utilities. Its deletions,
for instance, match saved email headers with those for the
corresponding message number in the server’s inbox, to ensure
accuracy. A similar test is performed on loads. On mismatches, the
mail index is automatically reloaded and updated. Unfortunately,
without additional state information, PyMailCGI cannot detect such
errors: it has no email list to compare against when messages are
viewed or deleted, only the message number in a link or hidden form
field.
In the worst case, PyMailCGI cannot guarantee that deletes
remove the intended
mail—
it’s
unlikely but not impossible that a mail earlier in the list may have
been deleted between the time message numbers were fetched and a mail
is deleted at the server. Without extra state information on the
server, PyMailCGI cannot use the safe deletion or synchronization
error checks in themailtools
modules to check whether subject message numbers are still
valid.
To guarantee safe deletes, PyMailCGI would require state
retention, which maps message numbers passed in pages to saved mail
headers fetched when the numbers were last determined, or a broader
policy, which sidesteps the issue completely. The next three sections
outline suggested improvements and potential exercises.
Perhaps the
simplest way to guarantee accurate deletions is to embed
the displayed message’s full header text in the message view page
itself, as hidden form fields, using the following scheme:
onViewListLink.py
Embed the header text in hidden form fields, escaped per
HTML conventions withcgi.escape
(with itsquote
argument set toTrue
to translate any nested quotes in
the header text).
onViewPageAction.py
Retrieve the embedded header text from the form’s input
fields, and pass it along to the safe deletion call inmailtools
for header matching.
This would be a small code change, but it might require an extra
headers fetch in the first of these scripts (it currently loads the
full mail text), and it would require building a phony list to
represent all mails’ headers (we would have headers for and delete
only one mail here). Alternatively, the header text could be extracted
from the fetched full mail text, by splitting on the blank line that
separates headers and message body text.
Moreover, this would increase the size of the data transmitted
both from client and server—mail header text is commonly greater than
1 KB in size, and it may be larger. This is a small amount of extra
data in modern terms, but it’s possible that this may run up against
size limitations in some client or server systems.
And really, this scheme is incomplete. It addresses only
deletion accuracy and does nothing about other synchronization errors
in general. For example, the system still may fetch and display the
wrong message from a message list page, after deletions of mails
earlier in the inbox performed elsewhere. In fact, this technique
guarantees only that the message displayed in a view window will be
the one deleted for that view window’s delete action. It does not
ensure that the mail displayed or deleted in the view window
corresponds to the selection made by the user in the mail index
list.
More specifically, because this scheme embeds headers in the
HTML of view windows, its header matching on deletion is useful only
if messages earlier in the inbox are deleted elsewhere
after
a mail has already been opened for viewing.
If the inbox is changed elsewhere
before
a mail
is opened in a view window, the wrong mail may be fetched from the
index page. In that event, this scheme avoids deleting a mail other
than the one displayed in a view window, but it assumes the user will
catch the mistake and avoid deleting if the wrong mail is loaded from
the index page. Though such cases are rare, this behavior is less than
user friendly.
Even though it is incomplete, this change does at least avoid
deleting the wrong email if the server’s inbox changes while a message
is being viewed—the mail displayed will be the only one deleted. A
working but tentative implementation of this scheme is implemented in
the following directory of the book’s examples distribution:
PP4E\Internet\Web\dev\PyMailCGI_2.1
When developed, it worked under the Firefox web browser and it
requires just more than 10 lines of code changes among three source
files, listed here (search for “
#EXPERIMENTAL
” to find the changes made
in the source files yourself):
# onViewListLink.py
. . .
hdrstext = fulltext.split('\n\n')[0] # use blank line
commonhtml.viewpage( # encodes passwd
msgnum, message, content, form, hdrstext, parts)
# commonhtml.py
. . .
def viewpage(msgnum, headers, text, form, hdrstext, parts=[]):
. . .
# delete needs hdrs text for inbox sync tests: can be multi-K large
hdrstext = cgi.escape(hdrstext, quote=True) # escape '"' too
print('' % hdrstext)
# onViewPageAction.py
. . .
fetcher = mailtools.SilentMailFetcher(site, user, pswd)
#fetcher.deleteMessages([msgnum])
hdrstext = getfield(form, 'Hdrstext') + '\n'
hdrstext = hdrstext.replace('\r\n', '\n') # get \n from top
dummyhdrslist = [None] * msgnum # only one msg hdr
dummyhdrslist[msgnum-1] = hdrstext # in hidden field
fetcher.deleteMessagesSafely([msgnum], dummyhdrslist) # exc on sync err
commonhtml.confirmationpage('Delete')
To run this version locally, run thewebserver
script from
Example 15-1
(in
Chapter 15
) with thedev
subdirectory name, and a unique port
number if you want to run both the original and the experimental
versions. For instance:
C:\...\PP4E\Internet\Web>webserver.py dev\PyMailCGI_2.1 9000
command linehttp://localhost:9000/pymailcgi.html
web browser URL
Although this version works on browsers tested, it is considered
tentative (and was not used for this chapter, and not updated for
Python 3.X in this edition) because it is an incomplete solution. In
those rare cases where the server’s inbox changes in ways that
invalidate message numbers after server fetches, this version avoids
inaccurate deletions, but index lists may still become out of sync.
Messages fetches may still be inaccurate, and addressing this likely
entails more sophisticated state retention options.
Note that in most cases, themessage-id
header would be sufficient for
matching against mails to be deleted in the inbox, and it might be all
that is required to pass from page to page. However, because this
field is optional and can be forged to have any value, this might not
always be a reliable way to identify matched messages; full header
matching is necessary to be robust. See the discussion ofmailtools
in
Chapter 13
for more
details.
The main limitation of the prior section’s technique is that it
addressed only deletions of already fetched emails. To catch other
kinds of inbox synchronization errors, we would have to also record
headers fetched when the index list page was constructed.
Since the index list page uses URL query parameters to record
state, adding large header texts as an additional parameter on the
URLs is not likely a viable option. In principle, the header text of
all mails in the list could be embedded in the index page as a single
hidden field, but this might add prohibitive size and transmission
overheads.
As a perhaps more complete approach, each time the mail index
list page is generated inonViewPswdSubmit.py
, fetched headers of all
messages could be saved in a flat file on the server, with a generated
unique name (possibly from time, process ID, and username). That
file’s name could be passed along with message numbers in pages as an
extra hidden field or query parameter.
On deletions, the header’s filename could be used byonViewPageAction.py
to load the saved
headers from the flat file, to be passed to the safe delete call inmailtools
. On fetches, the header
file could also be used for general synchronization tests to avoid
loading and displaying the wrong mail. Some sort of aging scheme would
be required to delete the header save files eventually (the index page
script might clean up old files), and we might also have to consider
multiuser issues.
This scheme essentially uses server-side files to emulate
PyMailGUI’s in-process memory, though it is complicated by the fact
that users may back up in their browser—deleting from view pages
fetched with earlier list pages, attempting to refetch from an earlier
list page and so on. In general, it may be necessary to analyze all
possible forward and backward flows through pages (it is essentially a
state machine). Header save files might also be used to detect
synchronization errors on fetches and may be removed on deletions to
effectively disable actions in prior page states, though header
matching may suffice to ensure deletion accuracy.
As a final alternative, mail clients could delete all email off
the server as soon as it is downloaded, such that deletions wouldn’t
impact POP identifiers (Microsoft Outlook may use this scheme by
default, for instance). However, this requires additional mechanisms
for storing deleted email persistently for later access, and it means
you can view fetched mail only on the machine to which it was
downloaded. Since both PyMailGUI and PyMailCGI are intended to be used
on a variety of machines, mail is kept on the POP server by
default.
Because of the current lack of inbox synchronization error
checks in PyMailCGI, you should not delete mails with it in an
important account, unless you employ one of the solution schemes
described or you use other tools to save mails to be deleted before
deletion. Adding state retention to ensure general inbox
synchronization may make an interesting exercise, but would also add
more code than we have space for here, especially if generalized for
multiple simultaneous site users.
This section
presents the source code of the utility modules imported and
used by the page scripts shown earlier. As installed, all of these modules
live in the same directory as the CGI scripts, to make imports simple—they
are found in the current working directory. There aren’t any new
screenshots to see here because these are utilities, not CGI scripts.
Moreover, these modules aren’t all that useful to study in isolation and
are included here primarily to be referenced as you go through the CGI
scripts’ code listed previously. See earlier in this chapter for
additional details not repeated here.
When running
PyMailCGI out of its own directory in the book examples
distribution tree, it relies on a number of external modules that are
potentially located elsewhere. Because all of these are accessible from
the
PP4E
package root, they can be imported with
dotted-path names as usual, relative to the root. In case this setup
ever changes, though, the module in
Example 16-10
encapsulates the
location of all external dependencies; if they ever move, this is the
only file that must be changed.
Example 16-10. PP4E\Internet\Web\PyMailCgi\cgi-bin\externs.py
"""
isolate all imports of modules that live outside of the PyMailCgi
directory, so that their location must only be changed here if moved;
we reuse the mailconfig settings that were used for pymailgui2 in ch13;
PP4E/'s container must be on sys.path to use the last import here;
"""
import sys
#sys.path.insert(0, r'C:\Users\mark\Stuff\Books\4E\PP4E\dev\Examples')
sys.path.insert(0, r'..\..\..\..') # relative to script dir
import mailconfig # local version
from PP4E.Internet.Email import mailtools # mailtools package
This module simply preimports all external names needed by
PyMailCGI into its own namespace. See
Chapter 13
for more on themailtools
package modules’ source code
imported and reused here; as for PyMailGUI, much of the magic behind
PyMailCGI is actually implemented inmailtools
.
This version of PyMailCGI has its own local copy of themailconfig
module we coded in
Chapter 13
and expanded in
Chapter 14
, but it simply loads all attributes
from the version we wrote in
Chapter 13
to
avoid redundancy, and customizes as desired; the local version is listed
in
Example 16-11
.
Example 16-11. PP4E\Internet\Email\PyMailCgi\cgi-bin\mailconfig.py
"""
user configuration settings for various email programs (PyMailCGI version);
email scripts get their server names and other email config options from
this module: change me to reflect your machine names, sig, and preferences;
"""
from PP4E.Internet.Email.mailconfig import * # reuse ch13 configs
fetchlimit = 50 # 4E: maximum number headers/emails to fetch on loads (dflt=25)
Our next utility module,
theloadmail
file in
Example 16-12
, depends on
external files and encapsulates access to mail on the remote POP server
machine. It currently exports one function,loadmailhdrs
, which returns a list of the
header text (only) of all mail in the specified POP account; callers are
unaware of whether this mail is fetched over the Net, lives in memory,
or is loaded from a persistent storage medium on the CGI server machine.
That is by design—becauseloadmail
changes won’t impact its clients, it is mostly a hook for future
expansion.
Example 16-12. PP4E\Internet\Web\PyMailCgi\cgi-bin\loadmail.py
"""
mail list loader; future--change me to save mail list between CGI script runs,
to avoid reloading all mail each time; this won't impact clients that use the
interfaces here if done well; for now, to keep this simple, reloads all mail
for each list page; 2.0+: we now only load message headers (via TOP), not full
msg, but still fetch all hdrs for each index list--in-memory caches don't work
in a stateless CGI script, and require a real (likely server-side) database;
"""
from commonhtml import runsilent # suppress prints (no verbose flag)
from externs import mailtools # shared with PyMailGUI
# load all mail from number 1 up
# this may trigger an exception
import sys
def progress(*args): # not used
sys.stderr.write(str(args) + '\n')
def loadmailhdrs(mailserver, mailuser, mailpswd):
fetcher = mailtools.SilentMailFetcher(mailserver, mailuser, mailpswd)
hdrs, sizes, full = fetcher.downloadAllHeaders() # get list of hdr text
return hdrs
This module is not much to look at—just an interface and calls to
other modules. Themailtools.SilentMailFetcher
class (reused here
from
Chapter 13
) uses the Pythonpoplib
module to fetch
mail over sockets. The silent class preventsmailtools
print call statements from going to
the HTML reply stream (although any exceptions are allowed to propagate
there normally).
In this version,loadmail
loads
just the header text portions of all incoming email to generate the
selection list page. However, it still reloads headers every time you
refetch the selection list page. As mentioned earlier, this scheme is
better than the prior version, but it can still be slow if you have lots
of email sitting on your server. Server-side database techniques,
combined with a scheme for invalidating message lists on deletions and
new receipts, might alleviate some of this bottleneck. Because the
interface exported byloadmail
would
likely not need to change to introduce a caching mechanism, clients of
this module would likely still work unchanged.
We
discussed PyMailCGI’s
security protocols in the abstract earlier in this
chapter. Here, we look at their concrete implementation. PyMailCGI
passes user and password state information from page to page using
hidden form fields and URL query parameters embedded in HTML reply
pages. We studied these techniques in the prior chapter. Such data is
transmitted as simple text over network sockets—within the HTML reply
stream from the server, and as parameters in the request from the
client. As such, it is subject to security issues.
This isn’t a concern if you are running a local web server on your
machine, as all our examples do. The data is being shipped back and
forth between two programs running on your computer, and it is not
accessible to the outside world. If you want to install PyMailCGI on a
remote web server machine, though, this can be an issue. Because this
data is sensitive, we’d ideally like some way to hide it in transit and
prevent it from being viewed in server logs. The policies used to
address this have varied across this book’s lifespan, as options have
come and gone:
The second edition of this book developed a custom encryption
module using the standard library’srotor
encryption module. This module was
used to encrypt data inserted into the server’s reply stream, and
then to later decrypt it when it was returned as a parameter from
the client. Unfortunately, in Python 2.4 and later, therotor
module is no longer available in the
standard library; it was withdrawn due to security concerns. This
seems a somewhat extreme measure (rotor
was adequate for simpler
applications), butrotor
is no
longer a usable solution in recent releases.
The third edition of this book extended the model of the
second, by adding support for encrypting passwords with the
third-party and open source
PyCrypto system. Regrettably, this system is
available for Python 2.X but still not for 3.X as I write these
words for the fourth edition in mid-2010 (though some progress on a
3.X port has been made). Moreover, the Python web server classes
used by the locally running server deployed for this book still does
not support HTTPS in
Python
3.1—
the ultimate solution to web security, which I’ll say
more about in a moment.
Because of all the foregoing, this fourth edition has legacy
support for bothrotor
and
PyCrypto if they are installed, but falls back on a simplistic
password obfuscator which may be different at each PyMailCGI
installation. Since this release is something of a prototype in
general, further refinement of this model, including support for
HTTPS under more robust web servers, is left as exercise.
In general, there are a variety of approaches to encrypting
information transferred back and forth between client and server.
Unfortunately again, none is easily implemented for this chapter’s
example, none is universally applicable, and most involve tools or
techniques that are well beyond the scope and size constraints of this
text. To sample some of the available options, though, the sections that
follow contain a brief rundown of some of the common techniques in this
domain.
In principle, CGI scripts can manually encrypt any sensitive
data they insert into reply streams, as PyMailCGI did in this book’s
second edition. With the removal of therotor
module,
though, Python 2.4’s standard library has no encryption tools for this
task. Moreover, using the originalrotor
module’s code is not advisable from a
maintenance perspective and would not be straightforward, since it was
coded in the C language (it’s not a simple matter of copying a
.py
file from a prior release). Unless you are
using an older version of Python,rotor
is not a real option.
Mostly for historical interest and comparison today, this module
was used as follows. It was based on an Enigma-style encryption
scheme: we make a new rotor object with a key (and optionally, a rotor
count) and call methods to encrypt and decrypt:
>>>import rotor
>>>r = rotor.newrotor('pymailcgi')
# (key, [,numrotors])
>>>r.encrypt('abc123')
# may return nonprintable chars
' \323an\021\224'
>>>x = r.encrypt('spam123')
# result is same len as input
>>>x
'* _\344\011pY'
>>>len(x)
7
>>>r.decrypt(x)
'spam123'
Notice that the samerotor
object can encrypt multiple strings, that the result may contain
nonprintable characters (printed as\ascii
escape codes when displayed), and
that the result is always the same length as the original string. Most
important, a string encrypted withrotor
can be decrypted in a different
process (e.g., in a later CGI script) if we re-create therotor
object:
>>>import rotor
>>>r = rotor.newrotor('pymailcgi')
# can be decrypted in new process
>>>r.decrypt('* _\344\011pY')
# use "\ascii" escapes for two chars
'spam123'
Oursecret
module by default
simply usedrotor
to encrypt and
did no additional encoding of its own. It relies on URL encoding when
the password is embedded in a URL parameter and on HTML escaping when
the password is embedded in hidden form fields. For URLs, the
following sorts of calls occur:
>>>from secret import encode, decode
>>>x = encode('abc$#<>&+')
# CGI scripts do this
>>>x
' \323a\016\317\326\023\0163'
>>>import urllib.parse
# urlencode does this
>>>y = urllib.parse.quote_plus(x)
>>>y
'+%d3a%0e%cf%d6%13%0e3'
>>>a = urllib.parse.unquote_plus(y)
# cgi.FieldStorage does this
>>>a
' \323a\016\317\326\023\0163'
>>>decode(a)
# CGI scripts do this
'abc$#<>&+'
Althoughrotor
itself is not
a widely viable option today, these same techniques can be used with
other encryption schemes.
A variety of encryption tools are available in the third-party
public domain, including the popular Python Cryptography Toolkit, also
known as
PyCrypto. This package adds built-in modules for
private and public key algorithms such as AES, DES, IDEA, and RSA
encryption, provides a Python module for reading and decrypting PGP
files, and much more. Here is an example of using AES encryption, run
after installing PyCrypto on my machine with a Windows
self-installer:
>>>from Crypto.Cipher import AES>>> AES.block_size16
>>>mykey = 'pymailcgi'.ljust(16, '-')
# key must be 16, 24, or 32 bytes
>>>mykey
'pymailcgi-------'
>>>
>>>password = 'Already got one.'
# length must be multiple of 16
>>>aesobj1 = AES.new(mykey, AES.MODE_ECB)
>>>cyphertext = aesobj1.encrypt(password)
>>>cyphertext
'\xfez\x95\xb7\x07_"\xd4\xb6\xe3r\x07g~X]'
>>>
>>>aesobj2 = AES.new(mykey, AES.MODE_ECB)
>>>aesobj2.decrypt(cyphertext)
'Already got one.'
This interface is similar to that of the originalrotor
module, but it uses better encryption
algorithms. AES is a popular private key encryption algorithm. It
requires a fixed length key and a data string to have a length that is
a multiple of 16 bytes.
Unfortunately, this is not part of standard Python, may be
subject to U.S. (and other countries’) export controls in binary form
at this writing, and is too large and complex a topic for us to
address in this text. This makes it less than universally applicable;
at the least, shipping its binary installer with this book’s examples
package may require legal expertise. And since data encryption is a
core requirement of PyMailCGI, this seems too strong an external
dependency.
The real showstopper for this book’s fourth edition, though, is
that PyCrypto is a 2.X-only system not yet available for Python 3.X
today; this makes it unusable with the examples in this book. Still,
if you are able to install and learn PyCrypto, this can be a powerful
solution. For more details, search for PyCrypto on the Web.
Provided you are using a server that supports secure HTTP, you
can simply write HTML and delegate the encryption to the web server
and browser. As long as both ends of the transmission support this
protocol, it is probably the ultimate encrypting solution for web
security. In fact, it is used by most e-commerce sites on the Web
today.
Secure HTTP (HTTPS)
is designated in URLs by using the protocol namehttps://
rather thanhttp://
. Under HTTPS, data is still sent
with the usual HTTP protocol, but it is encrypted with the SSL secure
sockets layer. HTTPS is supported by most web browsers and can be
configured in most web servers, including Apache and the
webserver.py
script that we are
running locally in this chapter. If SSL support is compiled into your
Python, Python sockets support it withssl
module socket wrappers, and the
client-side moduleurllib.request
we met in
Chapter 13
supports
HTTPS.
Unfortunately, enabling secure HTTP in a web server requires
more configuration and background knowledge than we can cover here,
and it may require installing tools outside the standard Python
release. If you want to explore this issue further, search the Web for
resources on setting up a Python-coded HTTPS server that supports SSL
secure communications. As one possible lead, see the third-party
M2Crypto package’s OpenSSL wrapper support for password
encryption, HTTPS in urllib, and more; this could be a viable
alternative to manual encryption, but it is not yet available for
Python 3.X at this writing.
Also see the Web for more details on HTTPS in general. It is not
impossible that some of the HTTPS extensions for Python’s standard web
server classes may make their way into the Python standard library in
the future, but they have not in recent years, perhaps reflecting the
classes’ intended roles—they provide limited functionality for use in
locally running servers oriented toward testing, not
deployment.
It’s possible to
replace the form fields and query parameter PyMailCGI
currently generates with client-side cookies marked as secure. Such
cookies are automatically encrypted when sent. Unfortunately again,
marking a cookie as secure simply means that it can be transmitted
only if the communications channel with the host is secure. It does
not provide any additional encryption. Because of this, this option
really just begs the question; it still requires an HTTPS
server.
As you can probably tell, web security is a larger topic than we
have time to address here. Because of that, thesecret.py
module in
Example 16-13
finesses the issue,
by trying a variety of approaches in turn:
If you are able to fetch and install the third-party
PyCrypto system described earlier, the module will use that
package’s AES tools to manually encrypt password data when
transmitted together with a username.
If not, it will tryrotor
next, if you’re able to find and install the original rotor module
in the version of Python that you’re using.
And finally, it falls back on a very simplistic default
character code shuffling obfuscation scheme, which you can replace
with one of your own if you install this program on the Internet
at large.
See
Example 16-13
for
more details; it uses function definitions nested inif
statements to generate the selected
encryption scheme’s functions at run time.
Example 16-13. PP4E\Internet\Web\PyMailCgi\cgi-bin\secret.py
"""
###############################################################################
PyMailCGI encodes the POP password whenever it is sent to/from client over
the Net with a username, as hidden text fields or explicit URL params; uses
encode/decode functions in this module to encrypt the pswd--upload your own
version of this module to use a different encryption mechanism or key; pymail
doesn't save the password on the server, and doesn't echo pswd as typed,
but this isn't 100% safe--this module file itself might be vulnerable;
HTTPS may be better and simpler but Python web server classes don't support;
###############################################################################
"""
import sys, time
dayofweek = time.localtime(time.time())[6] # for custom schemes
forceReadablePassword = False
###############################################################################
# string encoding schemes
###############################################################################
if not forceReadablePassword:
###########################################################
# don't do anything by default: the urllib.parse.quote
# or cgi.escape calls in commonhtml.py will escape the
# password as needed to embed in URL or HTML; the
# cgi module undoes escapes automatically for us;
###########################################################
def stringify(old): return old
def unstringify(old): return old
else:
###########################################################
# convert encoded string to/from a string of digit chars,
# to avoid problems with some special/nonprintable chars,
# but still leave the result semi-readable (but encrypted);
# some browsers had problems with escaped ampersands, etc.;
###########################################################
separator = '-'
def stringify(old):
new = ''
for char in old:
ascii = str(ord(char))
new = new + separator + ascii # '-ascii-ascii-ascii'
return new
def unstringify(old):
new = ''
for ascii in old.split(separator)[1:]:
new = new + chr(int(ascii))
return new
###############################################################################
# encryption schemes: try PyCrypto, then rotor, then simple/custom scheme
###############################################################################
useCrypto = useRotor = True
try:
import Crypto
except:
useCrypto = False
try:
import rotor
except:
useRotor = False
if useCrypto:
#######################################################
# use third-party pycrypto package's AES algorithm
# assumes pswd has no '\0' on the right: used to pad
# change the private key here if you install this
#######################################################
sys.stderr.write('using PyCrypto\n')
from Crypto.Cipher import AES
mykey = 'pymailcgi3'.ljust(16, '-') # key must be 16, 24, or 32 bytes
def do_encode(pswd):
over = len(pswd) % 16
if over: pswd += '\0' * (16-over) # pad: len must be multiple of 16
aesobj = AES.new(mykey, AES.MODE_ECB)
return aesobj.encrypt(pswd)
def do_decode(pswd):
aesobj = AES.new(mykey, AES.MODE_ECB)
pswd = aesobj.decrypt(pswd)
return pswd.rstrip('\0')
elif useRotor:
#######################################################
# use the standard lib's rotor module to encode pswd
# this does a better job of encryption than code above
# unfortunately, it is no longer available in Py 2.4+
#######################################################
sys.stderr.write('using rotor\n')
import rotor
mykey = 'pymailcgi3'
def do_encode(pswd):
robj = rotor.newrotor(mykey) # use enigma encryption
return robj.encrypt(pswd)
def do_decode(pswd):
robj = rotor.newrotor(mykey)
return robj.decrypt(pswd)
else:
#######################################################
# use our own custom scheme as a last resort
# shuffle characters in some reversible fashion
# caveat: very simple -- replace with one of your own
#######################################################
sys.stderr.write('using simple\n')
adder = 1
def do_encode(pswd):
pswd = 'vs' + pswd + '48'
res = ''
for char in pswd:
res += chr(ord(char) + adder) # inc each ASCII code
return str(res)
def do_decode(pswd):
pswd = pswd[2:-2]
res = ''
for char in pswd:
res += chr(ord(char) - adder)
return res
###############################################################################
# top-level entry points
###############################################################################
def encode(pswd):
return stringify(do_encode(pswd)) # encrypt plus string encode
def decode(pswd):
return do_decode(unstringify(pswd))
In addition to encryption, this module also implements an
encoding method for already encrypted strings, which transforms them
to and from printable characters. By default, the encoding functions
do nothing, and the system relies on straight URL or HTML encoding of
the encrypted string. An optional encoding scheme translates the
encrypted string to a string of ASCII code digits separated by dashes.
Either encoding method makes nonprintable characters in the encrypted
string printable.
To illustrate, let’s test this module’s tools interactively. For
this test, we setforce
Readable
Password
toTrue
. The top-level entry points encode and
decode into printable characters (for illustration purposes, this test
reflects a Python 2.X installation where PyCrypto is
installed):
>>>from secret import *
using PyCrypto
>>>data = encode('[email protected]+')
>>>data
'-47-248-2-170-107-242-175-18-227-249-53-130-14-140-163-107'
>>>decode(data)
'[email protected]+'
But there are actually two steps to this—encryption and
printable encoding:
>>>raw = do_encode('[email protected]+')
>>>raw
'/\xf8\x02\xaak\xf2\xaf\x12\xe3\xf95\x82\x0e\x8c\xa3k'
>>>text = stringify(raw)
>>>text
'-47-248-2-170-107-242-175-18-227-249-53-130-14-140-163-107'
>>>len(raw), len(text)
(16, 58)
Here’s what the encoding looks like without the extra printable
encoding:
>>>raw = do_encode('[email protected]+')
>>>raw
'/\xf8\x02\xaak\xf2\xaf\x12\xe3\xf95\x82\x0e\x8c\xa3k'
>>>do_decode(raw)
'[email protected]+'