diff --git a/lib/modules/Crypto.pmod/SCRAM.pike b/lib/modules/Crypto.pmod/SCRAM.pike
deleted file mode 100644
index 8fab19e716e6a822de03fd0ec820856a698b7c90..0000000000000000000000000000000000000000
--- a/lib/modules/Crypto.pmod/SCRAM.pike
+++ /dev/null
@@ -1,207 +0,0 @@
-
-//! SCRAM, defined by @rfc{5802@}.
-//!
-//! This implements both the client- and the serverside.
-//! You normally run either the server or the client, but if you would
-//! run both (use a separate client and a separate server object!),
-//! the sequence would be:
-//!
-//! @[client_1] -> @[server_1] -> @[server_2] -> @[client_2] ->
-//! @[server_3] -> @[client_3]
-//!
-//! @note
-//!   This implementation does not pretend to support the full protocol.
-//!   Most notably optional extension arguments are not supported (yet).
-
-#pike __REAL_VERSION__
-#pragma strict_types
-#require constant(Crypto.Hash)
-
-private .Hash H;  // hash object
-
-private string(8bit) first, nonce;
-
-private string(7bit) encode64(string(8bit) raw) {
-  return MIME.encode_base64(raw, 1);
-}
-
-private string(7bit) randomstring() {
-  return encode64(random_string(18));
-}
-
-private .MAC.State HMAC(string(8bit) key) {
-  return H->HMAC(key);
-}
-
-private string(7bit) clientproof(string(8bit) salted_password) {
-  .MAC.State hmacsaltedpw = HMAC(salted_password);
-  salted_password = hmacsaltedpw("Client Key");
-  // Returns ServerSignature through nonce
-  nonce = encode64(HMAC(hmacsaltedpw("Server Key"))(first));
-  return encode64([string(8bit)]
-                  (salted_password ^ HMAC(H->hash(salted_password))(first)));
-}
-
-//! Step 0 in the SCRAM handshake, prior to creating the object,
-//! you need to have agreed with your peer on the hashfunction to be used.
-//!
-//! @param h
-//!   The hash object on which the SCRAM object should base its
-//!   operations. Typical input is @[Crypto.SHA256].
-//!
-//! @note
-//! If you are a client, you must use the @ref{client_*@} methods; if you are
-//! a server, you must use the @ref{server_*@} methods.
-//! You cannot mix both client and server methods in a single object.
-//!
-//! @seealso
-//!   @[client_1], @[server_1]
-protected void create(.Hash h) {
-  H = h;
-}
-
-//! Client-side step 1 in the SCRAM handshake.
-//!
-//! @param username
-//!   The username to feed to the server.  Some servers already received
-//!   the username through an alternate channel (usually during
-//!   the hash-function selection handshake), in which case it
-//!   should be omitted here.
-//!
-//! @returns
-//!   The client-first request to send to the server.
-//!
-//! @seealso
-//!   @[client_2]
-string(7bit) client_1(void|string username) {
-  nonce = randomstring();
-  return [string(7bit)](first = [string(8bit)]sprintf("n,,n=%s,r=%s",
-    username && username != "" ? Standards.IDNA.to_ascii(username, 1) : "",
-    nonce));
-}
-
-//! Server-side step 1 in the SCRAM handshake.
-//!
-//! @param line
-//!   The received client-first request from the client.
-//!
-//! @returns
-//!   The username specified by the client.  Returns null
-//!   if the response could not be parsed.
-//!
-//! @seealso
-//!   @[server_2]
-string server_1(string(8bit) line) {
-  constant format = "n,,n=%s,r=%s";
-  string username, r;
-  catch {
-    first = [string(8bit)]line[3..];
-    [username, r] = array_sscanf(line, format);
-    nonce = [string(8bit)]r;
-    r = Standards.IDNA.to_unicode(username);
-  };
-  return r;
-}
-
-//! Server-side step 2 in the SCRAM handshake.
-//!
-//! @param salt
-//!   The salt corresponding to the username that has been specified earlier.
-//!
-//! @param iters
-//!   The number of iterations the hashing algorithm should perform
-//!   to compute the authentication hash.
-//!
-//! @returns
-//!   The server-first challenge to send to the client.
-//!
-//! @seealso
-//!   @[server_3]
-string(7bit) server_2(string(8bit) salt, int iters) {
-  string response = sprintf("r=%s,s=%s,i=%d",
-    nonce += randomstring(), encode64(salt), iters);
-  first += "," + response + ",";
-  return [string(7bit)]response;
-}
-
-//! Client-side step 2 in the SCRAM handshake.
-//!
-//! @param line
-//!   The received server-first challenge from the server.
-//!
-//! @param pass
-//!   The password to feed to the server.
-//!
-//! @returns
-//!   The client-final response to send to the server.  If the response is
-//!   null, the server sent something unacceptable or unparseable.
-//!
-//! @seealso
-//!   @[client_3]
-string(7bit) client_2(string(8bit) line, string pass) {
-  constant format = "r=%s,s=%s,i=%d";
-  string r, salt;
-  int iters;
-  if (!catch([r, salt, iters] = array_sscanf(line, format))
-      && iters > 0
-      && has_prefix(r, nonce)) {
-    line = [string(8bit)]sprintf("c=biws,r=%s", r);
-    first = [string(8bit)]sprintf("%s,r=%s,s=%s,i=%d,%s",
-                                  first[3..], r, salt, iters, line);
-    if (pass != "")
-      pass = Standards.IDNA.to_ascii(pass);
-    salt = MIME.decode_base64(salt);
-    nonce = [string(8bit)]sprintf("%s,%s,%d", pass, salt, iters);
-    if (!(r = .SCRAM_get_salted_password(H, nonce))) {
-      r = [string(8bit)]H->pbkdf2([string(8bit)]pass, [string(8bit)]salt,
-                                  iters, H->digest_size());
-      .SCRAM_set_salted_password([string(8bit)]r, H, nonce);
-    }
-    salt = sprintf("%s,p=%s", line, clientproof([string(8bit)]r));
-    first = 0;                         // Free memory
-  } else
-    salt = 0;
-  return [string(7bit)]salt;
-}
-
-//! Final server-side step in the SCRAM handshake.
-//!
-//! @param line
-//!   The received client-final challenge and response from the client.
-//!
-//! @param salted_password
-//!   The salted (using the salt provided earlier) password belonging
-//!   to the specified username.
-//!
-//! @returns
-//!   The server-final response to send to the client.  If the response
-//!   is null, the client did not supply the correct credentials or
-//!   the response was unparseable.
-string(7bit) server_3(string(8bit) line,
- string(8bit) salted_password) {
-  constant format = "c=biws,r=%s,p=%s";
-  string r, p;
-  if (!catch([r, p] = array_sscanf(line, format))
-      && r == nonce) {
-    first += sprintf("c=biws,r=%s", r);
-    p = p == clientproof(salted_password) && sprintf("v=%s", nonce);
-  }
-  return [string(7bit)]p;
-}
-
-//! Final client-side step in the SCRAM handshake.  If we get this far, the
-//! server has already verified that we supplied the correct credentials.
-//! If this step fails, it means the server does not have our
-//! credentials at all and is an imposter.
-//!
-//! @param line
-//!   The received server-final verification response.
-//!
-//! @returns
-//!   True if the server is valid, false if the server is invalid.
-int(0..1) client_3(string(8bit) line) {
-  constant format = "v=%s";
-  string v;
-  return !catch([v] = array_sscanf(line, format))
-         && v == nonce;
-}
diff --git a/lib/modules/Crypto.pmod/module.pmod b/lib/modules/Crypto.pmod/module.pmod
index 31f6bce086a4be7d44ae54aac086aadacbe7b719..3a80b6a933d5e643ea548ad4a5b385d257a0d694 100644
--- a/lib/modules/Crypto.pmod/module.pmod
+++ b/lib/modules/Crypto.pmod/module.pmod
@@ -62,24 +62,6 @@ class Sign {
   protected State `()() { return State(); }
 }
 
-// Salted password cache for SCRAM
-
-private mapping (Hash:mapping(string:string(8bit)))
- SCRAM_salted_password_cache = ([]);
-
-final string(8bit) SCRAM_get_salted_password(Hash h, string key) {
-  mapping(string:string(8bit)) m = SCRAM_salted_password_cache[h];
-  return m && m[key];
-}
-
-final void SCRAM_set_salted_password(string(8bit) SaltedPassword,
- Hash h, string key) {
-  mapping(string:string(8bit)) m = SCRAM_salted_password_cache[h];
-  if (!m || sizeof(m) > 16)
-    SCRAM_salted_password_cache[h] = m = ([]);
-  m[key] = SaltedPassword;
-}
-
 //! Hashes a @[password] together with a @[salt] with the
 //! crypt_md5 algorithm and returns the result.
 //!
diff --git a/lib/modules/Crypto.pmod/testsuite.in b/lib/modules/Crypto.pmod/testsuite.in
index 3f33683c469aec233963175e2cba1d8f3cd51a2f..35ad79cd24e032bb4f7d7077130b12060e5f9278 100644
--- a/lib/modules/Crypto.pmod/testsuite.in
+++ b/lib/modules/Crypto.pmod/testsuite.in
@@ -557,8 +557,8 @@ test_do( add_constant("RSA") )
 define(test_SCRAM, [[
   cond_resolv($1, [[
     test_any([[
-      Crypto.SCRAM client = Crypto.SCRAM($1);
-      Crypto.SCRAM server = Crypto.SCRAM($1);
+      Crypto.Hash.SCRAM client = $1.SCRAM();
+      Crypto.Hash.SCRAM server = $1.SCRAM();
       return $2 == server->server_1(client->client_1($2)) &&
         client->client_3(server->server_3(
           client->client_2(server->server_2(MIME.decode_base64($5), $6), $4),
diff --git a/lib/modules/Sql.pmod/pgsql_util.pmod b/lib/modules/Sql.pmod/pgsql_util.pmod
index 815ca3e6595cb180d0e56ff6df42965a8f2a6a8c..5bd4b21c1bb2c8813513cf916a7c37e5553db86f 100644
--- a/lib/modules/Sql.pmod/pgsql_util.pmod
+++ b/lib/modules/Sql.pmod/pgsql_util.pmod
@@ -1432,7 +1432,7 @@ class proxy {
   final string host;
   final int(0..65535) port;
   private string database, user, pass;
-  private Crypto.SCRAM SASLcontext;
+  private Crypto.Hash.SCRAM SASLcontext;
   final Thread.Condition waitforauthready;
   final Thread.Mutex shortmux;
   final int readyforquerycount;
@@ -1785,7 +1785,7 @@ class proxy {
   #endif
                 }
                 if (k) {
-                  SASLcontext = Crypto.SCRAM(Crypto.SHA256);
+                  SASLcontext = Crypto.SHA256.SCRAM();
                   word = SASLcontext.client_1();
                   authresponse(({
                     "SCRAM-SHA-256", 0, sprintf("%4c", sizeof(word)), word
diff --git a/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike b/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike
index c1218d9eda9101d5d8e0dd5c1715306e9b76c064..82e4bd59899d01553b11f4999e4b23db08e4ac43 100644
--- a/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike
+++ b/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike
@@ -537,3 +537,207 @@ string(8bit) P_hash(string(8bit) password, string(8bit) salt,
   return res[..(bytes-1)];
 }
 
+// Salted password cache for SCRAM
+// FIXME: Consider mark as weak?
+private mapping(string:string(8bit)) SCRAM_salted_password_cache = ([]);
+
+final string(8bit) SCRAM_get_salted_password(string key) {
+  mapping(string:string(8bit)) m = SCRAM_salted_password_cache;
+  return m && m[key];
+}
+
+final void SCRAM_set_salted_password(string(8bit) SaltedPassword, string key) {
+  mapping(string:string(8bit)) m = SCRAM_salted_password_cache;
+  if (!m || sizeof(m) > 16)
+    SCRAM_salted_password_cache = m = ([]);
+  m[key] = SaltedPassword;
+}
+
+//! SCRAM, defined by @rfc{5802@}.
+//!
+//! This implements both the client- and the serverside.
+//! You normally run either the server or the client, but if you would
+//! run both (use a separate client and a separate server object!),
+//! the sequence would be:
+//!
+//! @[client_1] -> @[server_1] -> @[server_2] -> @[client_2] ->
+//! @[server_3] -> @[client_3]
+//!
+//! @note
+//! If you are a client, you must use the @ref{client_*@} methods; if you are
+//! a server, you must use the @ref{server_*@} methods.
+//! You cannot mix both client and server methods in a single object.
+//!
+//! @note
+//!   This implementation does not pretend to support the full protocol.
+//!   Most notably optional extension arguments are not supported (yet).
+//!
+//! @seealso
+//!   @[client_1], @[server_1]
+class SCRAM
+{
+  private string(8bit) first, nonce;
+
+  private string(7bit) encode64(string(8bit) raw) {
+    return MIME.encode_base64(raw, 1);
+  }
+
+  private string(7bit) randomstring() {
+    return encode64(random_string(18));
+  }
+
+  private string(7bit) clientproof(string(8bit) salted_password) {
+    _HMAC.State hmacsaltedpw = HMAC(salted_password);
+    salted_password = hmacsaltedpw("Client Key");
+    // Returns ServerSignature through nonce
+    nonce = encode64(HMAC(hmacsaltedpw("Server Key"))(first));
+    return encode64([string(8bit)]
+		    (salted_password ^ HMAC(hash(salted_password))(first)));
+  }
+
+  //! Client-side step 1 in the SCRAM handshake.
+  //!
+  //! @param username
+  //!   The username to feed to the server.  Some servers already received
+  //!   the username through an alternate channel (usually during
+  //!   the hash-function selection handshake), in which case it
+  //!   should be omitted here.
+  //!
+  //! @returns
+  //!   The client-first request to send to the server.
+  //!
+  //! @seealso
+  //!   @[client_2]
+  string(7bit) client_1(void|string username) {
+    nonce = randomstring();
+    return [string(7bit)](first = [string(8bit)]sprintf("n,,n=%s,r=%s",
+      username && username != "" ? Standards.IDNA.to_ascii(username, 1) : "",
+      nonce));
+  }
+
+  //! Server-side step 1 in the SCRAM handshake.
+  //!
+  //! @param line
+  //!   The received client-first request from the client.
+  //!
+  //! @returns
+  //!   The username specified by the client.  Returns null
+  //!   if the response could not be parsed.
+  //!
+  //! @seealso
+  //!   @[server_2]
+  string server_1(string(8bit) line) {
+    constant format = "n,,n=%s,r=%s";
+    string username, r;
+    catch {
+      first = [string(8bit)]line[3..];
+      [username, r] = array_sscanf(line, format);
+      nonce = [string(8bit)]r;
+      r = Standards.IDNA.to_unicode(username);
+    };
+    return r;
+  }
+
+  //! Server-side step 2 in the SCRAM handshake.
+  //!
+  //! @param salt
+  //!   The salt corresponding to the username that has been specified earlier.
+  //!
+  //! @param iters
+  //!   The number of iterations the hashing algorithm should perform
+  //!   to compute the authentication hash.
+  //!
+  //! @returns
+  //!   The server-first challenge to send to the client.
+  //!
+  //! @seealso
+  //!   @[server_3]
+  string(7bit) server_2(string(8bit) salt, int iters) {
+    string response = sprintf("r=%s,s=%s,i=%d",
+      nonce += randomstring(), encode64(salt), iters);
+    first += "," + response + ",";
+    return [string(7bit)]response;
+  }
+
+  //! Client-side step 2 in the SCRAM handshake.
+  //!
+  //! @param line
+  //!   The received server-first challenge from the server.
+  //!
+  //! @param pass
+  //!   The password to feed to the server.
+  //!
+  //! @returns
+  //!   The client-final response to send to the server.  If the response is
+  //!   null, the server sent something unacceptable or unparseable.
+  //!
+  //! @seealso
+  //!   @[client_3]
+  string(7bit) client_2(string(8bit) line, string(8bit) pass) {
+    constant format = "r=%s,s=%s,i=%d";
+    string(8bit) r, salt;
+    int iters;
+    if (!catch([r, salt, iters] = [array(string(8bit)|int)]
+				   array_sscanf(line, format))
+	&& iters > 0
+	&& has_prefix(r, nonce)) {
+      line = [string(8bit)]sprintf("c=biws,r=%s", r);
+      first = [string(8bit)]sprintf("%s,r=%s,s=%s,i=%d,%s",
+				    first[3..], r, salt, iters, line);
+      if (pass != "")
+	pass = [string(7bit)]Standards.IDNA.to_ascii(pass);
+      salt = MIME.decode_base64(salt);
+      nonce = [string(8bit)]sprintf("%s,%s,%d", pass, salt, iters);
+      if (!(r = SCRAM_get_salted_password(nonce))) {
+	r = pbkdf2(pass, salt, iters, digest_size());
+	SCRAM_set_salted_password(r, nonce);
+      }
+      salt = sprintf("%s,p=%s", line, clientproof(r));
+      first = 0;                         // Free memory
+    } else
+      salt = 0;
+    return [string(7bit)]salt;
+  }
+
+  //! Final server-side step in the SCRAM handshake.
+  //!
+  //! @param line
+  //!   The received client-final challenge and response from the client.
+  //!
+  //! @param salted_password
+  //!   The salted (using the salt provided earlier) password belonging
+  //!   to the specified username.
+  //!
+  //! @returns
+  //!   The server-final response to send to the client.  If the response
+  //!   is null, the client did not supply the correct credentials or
+  //!   the response was unparseable.
+  string(7bit) server_3(string(8bit) line,
+			string(8bit) salted_password) {
+    constant format = "c=biws,r=%s,p=%s";
+    string r, p;
+    if (!catch([r, p] = array_sscanf(line, format))
+	&& r == nonce) {
+      first += sprintf("c=biws,r=%s", r);
+      p = p == clientproof(salted_password) && sprintf("v=%s", nonce);
+    }
+    return [string(7bit)]p;
+  }
+
+  //! Final client-side step in the SCRAM handshake.  If we get this far, the
+  //! server has already verified that we supplied the correct credentials.
+  //! If this step fails, it means the server does not have our
+  //! credentials at all and is an imposter.
+  //!
+  //! @param line
+  //!   The received server-final verification response.
+  //!
+  //! @returns
+  //!   True if the server is valid, false if the server is invalid.
+  int(0..1) client_3(string(8bit) line) {
+    constant format = "v=%s";
+    string v;
+    return !catch([v] = array_sscanf(line, format))
+      && v == nonce;
+  }
+}