diff --git a/config-templates/authsources.php b/config-templates/authsources.php
index e80d69a0fae2727094a4aa78e346fc0403003efc..093e9dfaa5b5330e1140f72d593418e35023cfd7 100644
--- a/config-templates/authsources.php
+++ b/config-templates/authsources.php
@@ -74,6 +74,17 @@ $config = array(
 	),
 	*/
 
+	/*
+	'htpasswd' => array(
+		'authcrypt:Htpasswd',
+		'htpasswd_file' => '/var/www/foo.edu/legacy_app/.htpasswd',
+		'static_attributes' => array(
+			'eduPersonAffiliation' => array('member', 'employee'),
+			'Organization' => array('University of Foo'),
+		),
+	),
+	*/
+
 	/*
 	// This authentication source serves as an example of integration with an
 	// external authentication engine. Take a look at the comment in the beginning
diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php
index 2ca47b6587da28d30dce52e5be5235da276af583..76c1b188818f2c6bc9abae7ca9a0767ea3a0eda8 100644
--- a/lib/SimpleSAML/Utils/Crypto.php
+++ b/lib/SimpleSAML/Utils/Crypto.php
@@ -81,4 +81,63 @@ class SimpleSAML_Utils_Crypto {
 			return $crypted === $clear;
 		}
 	}
+
+	/**
+	 * This function generates an Apache 'apr1' password hash, which uses a modified
+	 * version of MD5: http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
+	 * @param $password  The unencrypted password
+	 * @param $salt      Optional salt
+	 */
+	public static function apr1Md5Hash($password, $salt = NULL) {
+		assert('is_string($password)');
+
+		$chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+		if(!$salt) {
+			$salt = substr(str_shuffle($allowed_chars), 0, 8);
+		}
+
+		$len = strlen($password);
+		$text = $password.'$apr1$'.$salt;
+		$bin = pack("H32", md5($password.$salt.$password));
+		for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); }
+		for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $password{0}; }
+		$bin = pack("H32", md5($text));
+		for($i = 0; $i < 1000; $i++) {
+			$new = ($i & 1) ? $password : $bin;
+			if ($i % 3) $new .= $salt;
+			if ($i % 7) $new .= $password;
+			$new .= ($i & 1) ? $bin : $password;
+			$bin = pack("H32", md5($new));
+		}
+		$tmp= '';
+		for ($i = 0; $i < 5; $i++) {
+			$k = $i + 6;
+			$j = $i + 12;
+			if ($j == 16) $j = 5;
+			$tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
+		}
+		$tmp = chr(0).chr(0).$bin[11].$tmp;
+		$tmp = strtr(
+			strrev(substr(base64_encode($tmp), 2)),
+			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+			$chars
+		);
+		return "$"."apr1"."$".$salt."$".$tmp;
+	}
+
+
+	/**
+	 * This function verifies an Apache 'apr1' password hash
+	 */
+	public static function apr1Md5Valid($crypted, $clear) {
+		assert('is_string($crypted)');
+		assert('is_string($clear)');
+		$pattern = '/^\$apr1\$([A-Za-z0-9\.\/]{8})\$([A-Za-z0-9\.\/]{22})$/';
+
+		if(preg_match($pattern, $crypted, $matches)) {
+			$salt = $matches[1];
+			return ( $crypted == self::apr1Md5Hash($clear, $salt) );
+		}
+		return FALSE;
+	}
 }
diff --git a/modules/authcrypt/lib/Auth/Source/Htpasswd.php b/modules/authcrypt/lib/Auth/Source/Htpasswd.php
new file mode 100644
index 0000000000000000000000000000000000000000..88ba76819247664b81de5ec4606d96f034994d1b
--- /dev/null
+++ b/modules/authcrypt/lib/Auth/Source/Htpasswd.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * Authentication source for Apache 'htpasswd' files.
+ *
+ * @author Dyonisius (Dick) Visser, TERENA.
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_authcrypt_Auth_Source_Htpasswd extends sspmod_core_Auth_UserPassBase {
+
+
+	/**
+	 * Our users, stored in an array, where each value is "<username>:<passwordhash>".
+	 */
+	private $users;
+
+	/**
+	 * Constructor for this authentication source.
+	 *
+	 * @param array $info  Information about this authentication source.
+	 * @param array $config  Configuration.
+	 */
+	public function __construct($info, $config) {
+		assert('is_array($info)');
+		assert('is_array($config)');
+
+		/* Call the parent constructor first, as required by the interface. */
+		parent::__construct($info, $config);
+
+		$this->users = array();
+
+		if(!$htpasswd = file_get_contents($config['htpasswd_file'])) {
+			throw new Exception('Could not read ' . $config['htpasswd_file']);
+		}
+
+		$this->users = explode("\n", trim($htpasswd));
+
+		try {
+			$this->attributes = SimpleSAML_Utilities::parseAttributes($config['static_attributes']);
+		} catch(Exception $e) {
+			throw new Exception('Invalid static_attributes in authentication source ' .
+				$this->authId . ': ' .	$e->getMessage());
+		}
+	}
+
+
+	/**
+	 * Attempt to log in using the given username and password.
+	 *
+	 * On a successful login, this function should return the username as 'uid' attribute,
+	 * and merged attributes from the configuration file.
+	 * On failure, it should throw an exception. A SimpleSAML_Error_Error('WRONGUSERPASS')
+	 * should be thrown in case of a wrong username OR a wrong password, to prevent the
+	 * enumeration of usernames.
+	 *
+	 * @param string $username  The username the user wrote.
+	 * @param string $password  The password the user wrote.
+	 * @return array  Associative array with the users attributes.
+	 */
+	protected function login($username, $password) {
+		assert('is_string($username)');
+		assert('is_string($password)');
+
+		foreach($this->users as $userpass) {
+			$matches = explode(':', $userpass, 2);
+			if($matches[0] == $username) {
+
+				$crypted = $matches[1];
+
+				// This is about the only attribute we can add
+				$attributes = array_merge(array('uid' => array($username)), $this->attributes);
+
+				// Traditional crypt(3)
+				if(crypt($password, $crypted) == $crypted) {
+					SimpleSAML_Logger::debug('User '. $username . ' authenticated successfully');
+					return $attributes;
+				}
+
+				// Apache's custom MD5
+				if(SimpleSAML_Utils_Crypto::apr1Md5Valid($crypted, $password)) {
+					SimpleSAML_Logger::debug('User '. $username . ' authenticated successfully');
+					return $attributes;
+				}
+
+				// SHA1 or plain-text
+				if(SimpleSAML_Utils_Crypto::pwValid($crypted, $password)) {
+					SimpleSAML_Logger::debug('User '. $username . ' authenticated successfully');
+					return $attributes;
+				}
+				throw new SimpleSAML_Error_Error('WRONGUSERPASS');
+			}
+		}
+		throw new SimpleSAML_Error_Error('WRONGUSERPASS');
+	}
+
+}