#! /usr/bin/env perl # # Copyright (c) 2008,2018 William K. Cole. All rights reserved. # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice, this permission notice, and the following disclaimer # appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # crampass.pl: a simplistic script that replicates one function of the # dovecotpw tool that is included in the Dovecot mailstore # server, turning a password given on the command line into # a string that Dovecot can use as a 'CRAM-MD5' auth string. # This string consists of the two 16-byte state arrays that # are sometimes referred to as "contexts" and are generated # by feeding 2 differently padded versions of the password # into MD5 and recording the resulting (unfinalized) state # arrays. This is an intermediate step in the HMAC-MD5 # algorithm, and the resulting contexts can be used to # initialize MD5 hash objects for authentication of any # message. The contexts cannot be programmatically converted # back to the original password, and so are marginally safer # to store on a server than a plaintext password, which is # the only other option for use with the CRAM-MD5 auth # mechanism. This script was written in response to a query # on the Dovecot mailing list by Douglas Willcocks on # 2008-04-11 and ensuing discussion. It is intended as a # demo, not a production tool. It assumes that the immediate # user is neither malicious nor a fool. # # References: RFC1321(MD5) RFC2195(CRAM-MD5) RFC2104(HMAC) # ID:draft-ietf-sasl-crammd5-09(CRAM-MD5 as SASL mech) # # Version: 1.1 # Major changes from 1.0: # * Has a version number # * Much better code hygiene # * Finds either doveadm or dovecotpw (if you have one) in debug mode use strict; use warnings; # Globals... # Whether we're debugging our $debug; # The Dovecot program used to validate results if we're debugging our $DA; # The password as provided our $pass; # The "shared secret" which is either $pass OR md5($pass) iff $pass is too long our $secret; # The inner and outer keys, MD5 objects, contexts, and hex-encoded contexts our $Ki; our $Ko; our $innermd5; our $outermd5; our $Ci; our $Co; our $innerhex; our $outerhex; # Be verbose and run 'doveadm pw' or 'dovecotpw' for comparison (must be in $PATH) if ( defined $ENV{'debug'} ) { $debug = 1 ; $DA = `which doveadm` ; chomp $DA; if ( -x $DA ) { $DA="$DA pw" ; } else { $DA = `which dovecotpw` ; chomp $DA; -x $DA or warn "Can't find doveadm or dovecotpw!" ; undef $DA } } # Note dependency: I shamelessly use private parts, which # were easier for me in 2008 than figuring out how to get the # same functionality from Digest::MD5 use Digest::Perl::MD5; @ARGV == 1 or die "usage: crampass.pl \n"; $pass = shift @ARGV; defined $debug and print "using '$pass' as the user password\n"; $secret=$pass; if (length $secret > 64) { defined $debug and print "hashing down long password\n"; $secret = Digest::Perl::MD5::md5($secret); } defined $debug and print "using '$secret' as the shared secret\n"; $Ki = "$secret" ^ (chr(0x36) x 64); $Ko = "$secret" ^ (chr(0x5c) x 64); defined $debug and print "HMAC keys: inner='$Ki' outer='$Ko'\n"; $innermd5 = Digest::Perl::MD5->new; $innermd5->add($Ki); $Ci = pack 'V4', @{$innermd5->{_state}}; $outermd5 = Digest::Perl::MD5->new; $outermd5->add($Ko); $Co = pack 'V4', @{$outermd5->{_state}}; $innerhex=Digest::Perl::MD5::_encode_hex($Ci); $outerhex=Digest::Perl::MD5::_encode_hex($Co); my $innerhd=$innermd5->hexdigest; my $outerhd=$outermd5->hexdigest; defined $debug and print "hex: inner='$innerhex' outer='$outerhex'\n"; defined $debug and print "hexdigest: inner='$innerhd' outer='$outerhd'\n"; print "{CRAM-MD5}$outerhex$innerhex\n"; if ( defined $DA ) { defined $debug and print "$DA says:\n"; defined $debug and system("$DA -s CRAM-MD5 -p \"$pass\""); } else { defined $debug and print "No Dovecot tools found\n"};