diff --git a/cc/subtle/common_enums.cc b/cc/subtle/common_enums.cc
index 62d4c30965277a2817d8b1d8e92594217cd6f9f2..8f4ea62b04b9b794030d9eb16b88bee974fdf5ba 100644
--- a/cc/subtle/common_enums.cc
+++ b/cc/subtle/common_enums.cc
@@ -58,6 +58,8 @@ std::string EnumToString(HashType type) {
     return "SHA1";
   case HashType::SHA256:
     return "SHA256";
+  case HashType::SHA384:
+    return "SHA384";
   case HashType::SHA512:
     return "SHA512";
   case HashType::UNKNOWN_HASH:
diff --git a/cc/subtle/common_enums.h b/cc/subtle/common_enums.h
index a1d1277ea4f17c0c26531eefefc4bab6d78dc153..aac78b658573809fd281c3dda7bcfff37ade3f3b 100644
--- a/cc/subtle/common_enums.h
+++ b/cc/subtle/common_enums.h
@@ -43,6 +43,7 @@ enum EcPointFormat {
 enum HashType {
   UNKNOWN_HASH = 0,
   SHA1 = 1,  // SHA1 for digital signature is deprecated but HMAC-SHA1 is fine.
+  SHA384 = 2,
   SHA256 = 3,
   SHA512 = 4,
 };
diff --git a/cc/subtle/common_enums_test.cc b/cc/subtle/common_enums_test.cc
index e52757e0da3e042e06e56157056c05f3480a78da..ce355028ef9b6d93f597ff9e978aa5715fdf4a8b 100644
--- a/cc/subtle/common_enums_test.cc
+++ b/cc/subtle/common_enums_test.cc
@@ -35,6 +35,7 @@ TEST_F(CommonEnumsTest, testEllipticCurveTypeToString) {
 TEST_F(CommonEnumsTest, testHashTypeToString) {
   EXPECT_EQ("SHA1", EnumToString(HashType::SHA1));
   EXPECT_EQ("SHA256", EnumToString(HashType::SHA256));
+  EXPECT_EQ("SHA384", EnumToString(HashType::SHA384));
   EXPECT_EQ("SHA512", EnumToString(HashType::SHA512));
   EXPECT_EQ("UNKNOWN_HASH", EnumToString(HashType::UNKNOWN_HASH));
   EXPECT_EQ("UNKNOWN_HASH: 42", EnumToString((HashType)42));
diff --git a/cc/util/enums.cc b/cc/util/enums.cc
index 6ea7e9da81126f0b86a73a60e694db53d7437a40..e39a8360f8b436ec831bd75dddb97a715b32c0f7 100644
--- a/cc/util/enums.cc
+++ b/cc/util/enums.cc
@@ -88,6 +88,8 @@ pb::HashType Enums::SubtleToProto(subtle::HashType type) {
     return pb::HashType::SHA1;
   case subtle::HashType::SHA256:
     return pb::HashType::SHA256;
+  case subtle::HashType::SHA384:
+    return pb::HashType::SHA384;
   case subtle::HashType::SHA512:
     return pb::HashType::SHA512;
   default:
@@ -102,6 +104,8 @@ subtle::HashType Enums::ProtoToSubtle(pb::HashType type) {
     return subtle::HashType::SHA1;
   case pb::HashType::SHA256:
     return subtle::HashType::SHA256;
+  case pb::HashType::SHA384:
+    return subtle::HashType::SHA384;
   case pb::HashType::SHA512:
     return subtle::HashType::SHA512;
   default:
@@ -156,6 +160,8 @@ const char* Enums::HashName(pb::HashType hash_type) {
       return "SHA1";
     case pb::HashType::SHA256:
       return "SHA256";
+    case pb::HashType::SHA384:
+      return "SHA384";
     case pb::HashType::SHA512:
       return "SHA512";
     default:
@@ -208,6 +214,7 @@ pb::KeyStatusType Enums::KeyStatus(absl::string_view name) {
 pb::HashType Enums::Hash(absl::string_view name) {
   if (name == "SHA1") return pb::HashType::SHA1;
   if (name == "SHA256") return pb::HashType::SHA256;
+  if (name == "SHA384") return pb::HashType::SHA384;
   if (name == "SHA512") return pb::HashType::SHA512;
   return pb::HashType::UNKNOWN_HASH;
 }
diff --git a/cc/util/enums_test.cc b/cc/util/enums_test.cc
index 5f4f55291dbcfb7a9e61ab3970b2fe6f9ec4a30d..8af8126b97e985ed5c27985521b52f3a101bcb78 100644
--- a/cc/util/enums_test.cc
+++ b/cc/util/enums_test.cc
@@ -73,6 +73,8 @@ TEST_F(EnumsTest, testHashType) {
             Enums::SubtleToProto(subtle::HashType::SHA1));
   EXPECT_EQ(pb::HashType::SHA256,
             Enums::SubtleToProto(subtle::HashType::SHA256));
+  EXPECT_EQ(pb::HashType::SHA384,
+            Enums::SubtleToProto(subtle::HashType::SHA384));
   EXPECT_EQ(pb::HashType::SHA512,
             Enums::SubtleToProto(subtle::HashType::SHA512));
   EXPECT_EQ(pb::HashType::UNKNOWN_HASH,
@@ -84,6 +86,8 @@ TEST_F(EnumsTest, testHashType) {
             Enums::ProtoToSubtle(pb::HashType::SHA1));
   EXPECT_EQ(subtle::HashType::SHA256,
             Enums::ProtoToSubtle(pb::HashType::SHA256));
+  EXPECT_EQ(subtle::HashType::SHA384,
+            Enums::ProtoToSubtle(pb::HashType::SHA384));
   EXPECT_EQ(subtle::HashType::SHA512,
             Enums::ProtoToSubtle(pb::HashType::SHA512));
   EXPECT_EQ(subtle::HashType::UNKNOWN_HASH,
@@ -102,7 +106,7 @@ TEST_F(EnumsTest, testHashType) {
       count++;
     }
   }
-  EXPECT_EQ(4, count);
+  EXPECT_EQ(5, count);
 }
 
 TEST_F(EnumsTest, testEcPointFormat) {
@@ -218,6 +222,7 @@ TEST_F(EnumsTest, testHashName) {
 
   EXPECT_EQ(pb::HashType::SHA1, Enums::Hash("SHA1"));
   EXPECT_EQ(pb::HashType::SHA256, Enums::Hash("SHA256"));
+  EXPECT_EQ(pb::HashType::SHA384, Enums::Hash("SHA384"));
   EXPECT_EQ(pb::HashType::SHA512, Enums::Hash("SHA512"));
   EXPECT_EQ(pb::HashType::UNKNOWN_HASH, Enums::Hash("Other string"));
   EXPECT_EQ(pb::HashType::UNKNOWN_HASH, Enums::Hash("UNKNOWN_HASH"));
@@ -232,7 +237,7 @@ TEST_F(EnumsTest, testHashName) {
       count++;
     }
   }
-  EXPECT_EQ(4, count);
+  EXPECT_EQ(5, count);
 }
 
 TEST_F(EnumsTest, testKeyMaterialName) {
diff --git a/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java b/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java
index 1223bb79449cd0bcd620324653473acbf3406520..3500417d2a08cbadfc8ff3118d3f6cd9d4c1d575 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java
@@ -24,7 +24,6 @@ import com.google.crypto.tink.proto.RsaSsaPkcs1Params;
 import com.google.crypto.tink.proto.RsaSsaPssParams;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.Enums;
-import com.google.crypto.tink.subtle.Validators;
 import java.security.GeneralSecurityException;
 
 final class SigUtil {
@@ -50,14 +49,17 @@ final class SigUtil {
     switch (curve) {
       case NIST_P256:
         // Using SHA512 for curve P256 is fine. However, only the 256 leftmost bits of the hash is
-        // used in signature computation. Therefore, we don't allow it here to prevent security's
+        // used in signature computation. Therefore, we don't allow it here to prevent security
         // illusion.
         if (hash != HashType.SHA256) {
           throw new GeneralSecurityException(INVALID_PARAMS);
         }
         break;
       case NIST_P384:
-        /* fall through */
+        if (hash != HashType.SHA384 && hash != HashType.SHA512) {
+          throw new GeneralSecurityException(INVALID_PARAMS);
+        }
+        break;
       case NIST_P521:
         if (hash != HashType.SHA512) {
           throw new GeneralSecurityException(INVALID_PARAMS);
@@ -77,7 +79,7 @@ final class SigUtil {
    */
   public static void validateRsaSsaPkcs1Params(RsaSsaPkcs1Params params)
       throws GeneralSecurityException {
-    Validators.validateSignatureHash(toHashType(params.getHashType()));
+    toHashType(params.getHashType());
   }
 
   /**
@@ -102,7 +104,7 @@ final class SigUtil {
    */
   public static void validateRsaSsaPssParams(RsaSsaPssParams params)
       throws GeneralSecurityException {
-    Validators.validateSignatureHash(toHashType(params.getSigHash()));
+    toHashType(params.getSigHash());
     if (params.getSigHash() != params.getMgf1Hash()) {
       throw new GeneralSecurityException("MGF1 hash is different from signature hash");
     }
@@ -111,15 +113,16 @@ final class SigUtil {
   /** Converts protobuf enum {@code HashType} to raw Java enum {@code Enums.HashType}. */
   public static Enums.HashType toHashType(HashType hash) throws GeneralSecurityException {
     switch (hash) {
-      case SHA1:
-        return Enums.HashType.SHA1;
       case SHA256:
         return Enums.HashType.SHA256;
+      case SHA384:
+        return Enums.HashType.SHA384;
       case SHA512:
         return Enums.HashType.SHA512;
       default:
-        throw new GeneralSecurityException("unknown hash type: " + hash);
+        break;
     }
+    throw new GeneralSecurityException("unsupported hash type: " + hash.name());
   }
 
   /** Converts protobuf enum {@code EllipticCurveType} to raw Java enum {code CurveType}. */
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel b/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
index 90a76e00c8de69b8dc213411dac6a0998b62e06d..57b916a12142a288e1144265c1a097d67b64e3d4 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
+++ b/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
@@ -26,6 +26,7 @@ java_library(
         "Enums.java",
         "Hex.java",
         "ImmutableByteArray.java",
+        "PemKeyType.java",
         "Random.java",
         "SubtleUtil.java",
         "Validators.java",
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java b/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java
index 52b17ca723e1fc8187391fe758189c9aa0c4ee5d..0980deec83bc1648b6b272d78d9e8d4aadee11ec 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java
@@ -132,10 +132,10 @@ public final class EllipticCurves {
       throw new GeneralSecurityException("point is at infinity");
     }
     // Check 0 <= x < p and 0 <= y < p.
-    if (x.signum() == -1 || x.compareTo(p) != -1) {
+    if (x.signum() == -1 || x.compareTo(p) >= 0) {
       throw new GeneralSecurityException("x is out of range");
     }
-    if (y.signum() == -1 || y.compareTo(p) != -1) {
+    if (y.signum() == -1 || y.compareTo(p) >= 0) {
       throw new GeneralSecurityException("y is out of range");
     }
     // Check y^2 == x^3 + a x + b (mod p)
@@ -216,7 +216,7 @@ public final class EllipticCurves {
    * @param curve must be a prime order elliptic curve
    * @return the size of an element in bits
    */
-  private static int fieldSizeInBits(EllipticCurve curve) throws GeneralSecurityException {
+  static int fieldSizeInBits(EllipticCurve curve) throws GeneralSecurityException {
     return getModulus(curve).subtract(BigInteger.ONE).bitLength();
   }
 
@@ -717,7 +717,7 @@ public final class EllipticCurves {
             throw new GeneralSecurityException("invalid format");
           }
           BigInteger x = new BigInteger(1, Arrays.copyOfRange(encoded, 1, encoded.length));
-          if (x.signum() == -1 || x.compareTo(p) != -1) {
+          if (x.signum() == -1 || x.compareTo(p) >= 0) {
             throw new GeneralSecurityException("x is out of range");
           }
           BigInteger y = getY(x, lsb, curve);
@@ -911,7 +911,7 @@ public final class EllipticCurves {
       throws GeneralSecurityException {
     EllipticCurve privateKeyCurve = privateKey.getParams().getCurve();
     BigInteger x = new BigInteger(1, secret);
-    if (x.signum() == -1 || x.compareTo(getModulus(privateKeyCurve)) != -1) {
+    if (x.signum() == -1 || x.compareTo(getModulus(privateKeyCurve)) >= 0) {
       throw new GeneralSecurityException("shared secret is out of range");
     }
     // This will throw if x is not a valid coordinate.
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/Enums.java b/java/src/main/java/com/google/crypto/tink/subtle/Enums.java
index 803b519f5ca9fb01a97384e3886e4848fd64106d..e66db606bb1d9cdb16ce88c357a27529a6d7f81d 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/Enums.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/Enums.java
@@ -22,6 +22,7 @@ public final class Enums {
   public enum HashType {
     SHA1, // Using SHA1 for digital signature is deprecated but HMAC-SHA1 is fine.
     SHA256,
+    SHA384,
     SHA512,
   };
 }
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/PemKeyType.java b/java/src/main/java/com/google/crypto/tink/subtle/PemKeyType.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5ae2b7ce6e5b9b01d9ba6163de726426db93d7a
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/subtle/PemKeyType.java
@@ -0,0 +1,151 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import com.google.crypto.tink.subtle.Enums.HashType;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/** PEM key types that Tink supports */
+public enum PemKeyType {
+  // RSASSA-PSS 2048 bit key with a SHA256 digest.
+  RSA_PSS_2048_SHA256("RSA", "RSASSA-PSS", 2048, HashType.SHA256),
+  // RSASSA-PSS 3072 bit key with a SHA256 digest.
+  RSA_PSS_3072_SHA256("RSA", "RSASSA-PSS", 3072, HashType.SHA256),
+  // RSASSA-PSS 4096 bit key with a SHA256 digest.
+  RSA_PSS_4096_SHA256("RSA", "RSASSA-PSS", 4096, HashType.SHA256),
+  // RSASSA-PSS 4096 bit key with a SHA512 digest.
+  RSA_PSS_4096_SHA512("RSA", "RSASSA-PSS", 4096, HashType.SHA512),
+
+  // RSASSA-PKCS1-v1_5 with a 2048 bit key and a SHA256 digest.
+  RSA_SIGN_PKCS1_2048_SHA256("RSA", "RSASSA-PKCS1-v1_5", 2048, HashType.SHA256),
+  // RSASSA-PKCS1-v1_5 with a 3072 bit key and a SHA256 digest.
+  RSA_SIGN_PKCS1_3072_SHA256("RSA", "RSASSA-PKCS1-v1_5", 3072, HashType.SHA256),
+  // RSASSA-PKCS1-v1_5 with a 4096 bit key and a SHA256 digest.
+  RSA_SIGN_PKCS1_4096_SHA256("RSA", "RSASSA-PKCS1-v1_5", 4096, HashType.SHA256),
+  // RSASSA-PKCS1-v1_5 with a 4096 bit key and a SHA512 digest.
+  RSA_SIGN_PKCS1_4096_SHA512("RSA", "RSASSA-PKCS1-v1_5", 4096, HashType.SHA512),
+
+  // ECDSA on the NIST P-256 curve with a SHA256 digest.
+  ECDSA_P256_SHA256("EC", "ECDSA", 256, HashType.SHA256),
+  // ECDSA on the NIST P-384 curve with a SHA384 digest.
+  ECDSA_P384_SHA384("EC", "ECDSA", 384, HashType.SHA384),
+  // ECDSA on the NIST P-521 curve with a SHA512 digest.
+  ECDSA_P521_SHA512("EC", "ECDSA", 521, HashType.SHA512);
+
+  public final String keyType;
+  public final String algorithm;
+  public final int keySizeInBits;
+  public final HashType hash;
+
+  PemKeyType(String keyType, String algorithm, int keySizeInBits, HashType hash) {
+    this.keyType = keyType;
+    this.algorithm = algorithm;
+    this.keySizeInBits = keySizeInBits;
+    this.hash = hash;
+  }
+
+  private static final String PUBLIC_KEY = "PUBLIC KEY";
+  private static final String PRIVATE_KEY = "PRIVATE KEY";
+  private static final String BEGIN = "-----BEGIN ";
+  private static final String END = "-----END ";
+  private static final String MARKER = "-----";
+
+  /**
+   * Reads a single key from {@code reader}.
+   *
+   * @return a {@link Key} or null if the reader doesn't contain a valid PEM.
+   */
+  public Key readKey(BufferedReader reader) throws IOException {
+    String line = reader.readLine();
+    while (line != null && !line.startsWith(BEGIN)) {
+      line = reader.readLine();
+    }
+    if (line == null) {
+      return null;
+    }
+
+    line = line.trim().substring(BEGIN.length());
+    int index = line.indexOf(MARKER);
+    if (index < 0) {
+      return null;
+    }
+    String type = line.substring(0, index);
+    String endMarker = END + type + MARKER;
+    StringBuilder base64key = new StringBuilder();
+
+    while ((line = reader.readLine()) != null) {
+      if (line.indexOf(":") > 0) {
+        // header, ignore
+        continue;
+      }
+      if (line.contains(endMarker)) {
+        break;
+      }
+      base64key.append(line);
+    }
+    try {
+      byte[] key = Base64.decode(base64key.toString(), Base64.DEFAULT);
+      if (type.contains(PUBLIC_KEY)) {
+        return getPublicKey(key);
+      } else if (type.contains(PRIVATE_KEY)) {
+        return getPrivateKey(key);
+      }
+    } catch (GeneralSecurityException | IllegalArgumentException ex) {
+      return null;
+    }
+    return null;
+  }
+
+  private Key getPublicKey(final byte[] key) throws GeneralSecurityException {
+    KeyFactory keyFactory = EngineFactory.KEY_FACTORY.getInstance(this.keyType);
+    return validate(keyFactory.generatePublic(new X509EncodedKeySpec(key)));
+  }
+
+  private Key getPrivateKey(final byte[] key) throws GeneralSecurityException {
+    KeyFactory keyFactory = EngineFactory.KEY_FACTORY.getInstance(this.keyType);
+    return validate(keyFactory.generatePrivate(new PKCS8EncodedKeySpec(key)));
+  }
+
+  private Key validate(Key key) throws GeneralSecurityException {
+    if (this.keyType.equals("RSA")) {
+      RSAKey rsaKey = (RSAKey) key;
+      int foundKeySizeInBits = rsaKey.getModulus().bitLength();
+      if (foundKeySizeInBits != this.keySizeInBits) {
+        throw new GeneralSecurityException(
+            String.format(
+                "invalid RSA key size, want %d got %d", this.keySizeInBits, foundKeySizeInBits));
+      }
+    } else {
+      ECKey ecKey = (ECKey) key;
+      int foundKeySizeInBits = EllipticCurves.fieldSizeInBits(ecKey.getParams().getCurve());
+      if (foundKeySizeInBits != this.keySizeInBits) {
+        throw new GeneralSecurityException(
+            String.format(
+                "invalid EC key size, want %d got %d", this.keySizeInBits, foundKeySizeInBits));
+      }
+    }
+    return key;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java b/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
index a56b6ac3aefa00668a4d68751401024f08c2afd8..fa9862656597e8b29ecc4e5b21268b1b4849c406 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
@@ -65,6 +65,8 @@ public class SubtleUtil {
         return "SHA-1";
       case SHA256:
         return "SHA-256";
+      case SHA384:
+        return "SHA-384";
       case SHA512:
         return "SHA-512";
     }
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/Validators.java b/java/src/main/java/com/google/crypto/tink/subtle/Validators.java
index b5bb2b8a96918b64836aaa29e0e90db152276f27..1c0adf0e5180c2cde9e21e64661ac42cd35bf627 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/Validators.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/Validators.java
@@ -80,12 +80,13 @@ public final class Validators {
   public static void validateSignatureHash(HashType hash) throws GeneralSecurityException {
     switch (hash) {
       case SHA256: // fall through
+      case SHA384: // fall through
       case SHA512:
         return;
-      case SHA1:
-        throw new GeneralSecurityException("SHA1 is not safe for signature");
+      default:
+        break;
     }
-    throw new GeneralSecurityException("Unsupported hash " + hash);
+    throw new GeneralSecurityException("Unsupported hash: " + hash.name());
   }
 
   /**
diff --git a/java/src/test/java/com/google/crypto/tink/signature/SigUtilTest.java b/java/src/test/java/com/google/crypto/tink/signature/SigUtilTest.java
index 58291f292a8d1b02d55c0e0d8b32917deb31d5ab..61f3f2cf5ef952766202548582bd1942e785dfde 100644
--- a/java/src/test/java/com/google/crypto/tink/signature/SigUtilTest.java
+++ b/java/src/test/java/com/google/crypto/tink/signature/SigUtilTest.java
@@ -31,8 +31,8 @@ import org.junit.runners.JUnit4;
 public final class SigUtilTest {
   @Test
   public void testToHashType() throws Exception {
-    assertEquals(Enums.HashType.SHA1, SigUtil.toHashType(HashType.SHA1));
     assertEquals(Enums.HashType.SHA256, SigUtil.toHashType(HashType.SHA256));
+    assertEquals(Enums.HashType.SHA384, SigUtil.toHashType(HashType.SHA384));
     assertEquals(Enums.HashType.SHA512, SigUtil.toHashType(HashType.SHA512));
     try {
       SigUtil.toHashType(HashType.UNKNOWN_HASH);
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/EcdsaSignJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/EcdsaSignJceTest.java
index d7089f38570134773b1f7171ee626c077d2d98bd..215b6f6406999c173d5ad5ca8ede5fe3e681db0f 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/EcdsaSignJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/EcdsaSignJceTest.java
@@ -75,7 +75,7 @@ public class EcdsaSignJceTest {
       fail("Unsafe hash, should have thrown exception.");
     } catch (GeneralSecurityException e) {
       // Expected.
-      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+      TestUtil.assertExceptionContains(e, "Unsupported hash: SHA1");
     }
   }
 
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
index 87d781e52f8f2d9bb9ea655b79e55ad1833c5786..007e5ed262af334e03bf562c41b5f3d2c31e7898 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
@@ -137,7 +137,7 @@ public class EcdsaVerifyJceTest {
       fail("Unsafe hash, should have thrown exception.");
     } catch (GeneralSecurityException e) {
       // Expected.
-      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+      TestUtil.assertExceptionContains(e, "Unsupported hash: SHA1");
     }
   }
 
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/PemKeyTypeTest.java b/java/src/test/java/com/google/crypto/tink/subtle/PemKeyTypeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0290146b71ed6f3f4f1a982f75f6ecd48788fc34
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/subtle/PemKeyTypeTest.java
@@ -0,0 +1,191 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.security.Key;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for PemKeyType */
+@RunWith(JUnit4.class)
+public final class PemKeyTypeTest {
+  @Before
+  public void setUp() {}
+
+  @Test
+  public void readKey_RsaPublicKey_shouldWork() throws Exception {
+    String pem =
+        "-----BEGIN PUBLIC KEY-----\n"
+            + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv90Xf/NN1lRGBofJQzJf\n"
+            + "lHvo6GAf25GGQGaMmD9T1ZP71CCbJ69lGIS/6akFBg6ECEHGM2EZ4WFLCdr5byUq\n"
+            + "GCf4mY4WuOn+AcwzwAoDz9ASIFcQOoPclO7JYdfo2SOaumumdb5S/7FkKJ70TGYW\n"
+            + "j9aTOYWsCcaojbjGDY/JEXz3BSRIngcgOvXBmV1JokcJ/LsrJD263WE9iUknZDhB\n"
+            + "K7y4ChjHNqL8yJcw/D8xLNiJtIyuxiZ00p/lOVUInr8C/a2C1UGCgEGuXZAEGAdO\n"
+            + "NVez52n5TLvQP3hRd4MTi7YvfhezRcA4aXyIDOv+TYi4p+OVTYQ+FMbkgoWBm5bq\n"
+            + "wQIDAQAB\n"
+            + "-----END PUBLIC KEY-----\n";
+    BufferedReader reader = new BufferedReader(new StringReader(pem));
+    Key key = PemKeyType.RSA_PSS_2048_SHA256.readKey(reader);
+    assertThat(key).isNotNull();
+    assertThat(key).isInstanceOf(RSAPublicKey.class);
+    RSAPublicKey rsa = (RSAPublicKey) key;
+    assertThat(rsa.getModulus()).isNotNull();
+    assertThat(rsa.getPublicExponent()).isNotNull();
+  }
+
+  @Test
+  public void readKey_RsaPrivateKey_shouldWork() throws Exception {
+    String pem =
+        "-----BEGIN PRIVATE KEY-----\n"
+            + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8Bn6pA4wksGPK\n"
+            + "xhRrJnk0mcyKk5hSCFlrlwCs1OUaWAQTMWzFrMW0mdR4FCG6mw2K91rla2F51af8\n"
+            + "IJjy/E02ampBZrFfIlTbHLPOXdSrgL2L1a213zS2AsMZ1NAEKZwG5eJDf9Ym4oTC\n"
+            + "nut50YILgwtwYHLvov0ciJjR6q85+59UznZx6itVEqQpDT7Fi7QWOaGb5mMLHCcF\n"
+            + "m5oyUFvrxvQrMB+fss8rYkwbZZhwK76u04tf2ZQdZh/2rcpl/7JR0fMUvO0IYfow\n"
+            + "7GduISnlrLoDpst1lPk8YM75sq7uRe3Gqt0x+EHuHzf9Y8z/POu7AYo9Yxs9SYp5\n"
+            + "NIcEu0GfAgMBAAECggEAcYsagcX6o01BdfoX6nzZRMJ7mlN28FLKbQZLChOmJjpw\n"
+            + "e4alQNoMqfsbK0g89gscKoclBNXLj19OihrFQjbKCcpJUCVLhz+cLpUun7hZ7RdZ\n"
+            + "X1AyDloz4pXYa4jv9ROLfT7lXA2erOytbzm4yV+TQJBqH/qebcfnQYvbfShTmJcp\n"
+            + "fH2lNYhn5g3+jHb79aakwGTg9q8b88lkDL7gB66jvoEBe3JtCItplXuET5UfrDI/\n"
+            + "8+ef1n2vMqPc6GIyCrD0p4JV90D3OBOWq41V+AwbOKFJ8kGKJ0d5W0SxQJL6F9IV\n"
+            + "rg4zx4mXRxq5cWKLiXd2qAu97n7d9g7KbOy6UPMigQKBgQDj8VJGeEn0wth/WmUG\n"
+            + "RTh4t1R5lrFAZ5ZuM2OZ4r5qjC6o8GUlHwXovc3kcz1whFI0MvOq1rdZkO+tvtvO\n"
+            + "kcsJfOK4Xfoi/TyhKoYZjXbTEAlTE1HwckaTfNex2B02dfiv11nRJ57bEwbhL3V7\n"
+            + "rzaOJl+0KXdbG00W2Ip7AJ8AxwKBgQDTK1fz0p90HDPM+V2YuTtO/VavD5vJj5CJ\n"
+            + "2HYezM9l4Lp/7r+++PzjuzikpflhTUeijxNyOFGKtH8KEpEtyVGx1UBjK8VwM4sX\n"
+            + "7k+GZ2e3upisagV/GisnEB7lhOnoLUqD8x7xTRHx2RBdw44wUqUGmC/zZ552DHrR\n"
+            + "hvNhKEyQaQKBgQDFNr+WlPB3wjUKSq1pdW5ck1GVOVn2fSlcAz5DoDhbexnLtOHt\n"
+            + "8h9stPt0kngv52wwGX1U7B0KcynLy3vmB6IBfXmzRivrJerVDjOj3A9YoWFP7UFR\n"
+            + "pa2GYddE2dS8j+kwSkQ9f+gjZxzmq+cbsgajinP3LoFD5CUYhRWbQnhPdQKBgDZw\n"
+            + "IxFhR+gH6Ta7Rmy7u9VmK/WfYXr5vro6imDwTbsmzw1yAA58Y71Vo4mWnA6AfKok\n"
+            + "lk/IwwSt+V4gYTrbfmsI3btzKkf9kasOrYOpnqxXt0ojXt1gYqWEW2Kx/Bb1rhMM\n"
+            + "Fvr/8lNVsQlrA3njpFVp4FqwaMJn/zWKw61VVT+ZAoGAOkcDDz6GihRX8CkK5ejh\n"
+            + "qV/vI/m42Qsg2OddE4yUvAHpki1gEmqK9scULrsyztCGtSzx+l3TibzmG/bGbsTJ\n"
+            + "1HzQiotarX2fSCAgA8wZvc4F0eQbVo5gxDrsRKIwMSgr1GrEfqd93yuKMDp4TifH\n"
+            + "P54N1bX5PnvnE2HC22dRMNQ=\n"
+            + "-----END PRIVATE KEY-----\n";
+    BufferedReader reader = new BufferedReader(new StringReader(pem));
+    Key key = PemKeyType.RSA_PSS_2048_SHA256.readKey(reader);
+    assertThat(key).isNotNull();
+    assertThat(key).isInstanceOf(RSAPrivateKey.class);
+    RSAPrivateKey rsa = (RSAPrivateKey) key;
+    assertThat(rsa.getModulus()).isNotNull();
+    assertThat(rsa.getPrivateExponent()).isNotNull();
+  }
+
+  @Test
+  public void readKey_EcPublicKey_shouldWork() throws Exception {
+    String pem =
+        "-----BEGIN PUBLIC KEY-----\n"
+            + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7BiT5K5pivl4Qfrt9hRhRREMUzj/\n"
+            + "8suEJ7GlMxZfvdcpbi/GhYPuJi8Gn2H1NaMJZcLZo5MLPKyyGT5u3u1VBQ==\n"
+            + "-----END PUBLIC KEY-----\n";
+    BufferedReader reader = new BufferedReader(new StringReader(pem));
+    Key key = PemKeyType.ECDSA_P256_SHA256.readKey(reader);
+    assertThat(key).isNotNull();
+    assertThat(key).isInstanceOf(ECPublicKey.class);
+  }
+
+  @Test
+  public void readKey_EcPrivateKey_shouldWork() throws Exception {
+    String pem =
+        "-----BEGIN PRIVATE KEY-----\n"
+            + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQghpeIjMYdV40aVFTt\n"
+            + "u8kJPLduSnj6HBamgrrZwAhKLrahRANCAAThRzShRQmj7MChwiZWH6k6PpksS5HM\n"
+            + "8xP2XD/CiUeWCLR8g30Zh9K7NvufcfZxyJ3I6NTilbGcEM5/VgqAt8z3\n"
+            + "-----END PRIVATE KEY-----\n";
+    BufferedReader reader = new BufferedReader(new StringReader(pem));
+    Key key = PemKeyType.ECDSA_P256_SHA256.readKey(reader);
+    assertThat(key).isNotNull();
+    assertThat(key).isInstanceOf(ECPrivateKey.class);
+  }
+
+  @Test
+  public void readKey_withCommentHeader_shouldWork() throws Exception {
+    String pem =
+        "-----BEGIN PUBLIC KEY-----\n"
+            + "Version: 1.0.0\n"
+            + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv90Xf/NN1lRGBofJQzJf\n"
+            + "lHvo6GAf25GGQGaMmD9T1ZP71CCbJ69lGIS/6akFBg6ECEHGM2EZ4WFLCdr5byUq\n"
+            + "GCf4mY4WuOn+AcwzwAoDz9ASIFcQOoPclO7JYdfo2SOaumumdb5S/7FkKJ70TGYW\n"
+            + "j9aTOYWsCcaojbjGDY/JEXz3BSRIngcgOvXBmV1JokcJ/LsrJD263WE9iUknZDhB\n"
+            + "K7y4ChjHNqL8yJcw/D8xLNiJtIyuxiZ00p/lOVUInr8C/a2C1UGCgEGuXZAEGAdO\n"
+            + "NVez52n5TLvQP3hRd4MTi7YvfhezRcA4aXyIDOv+TYi4p+OVTYQ+FMbkgoWBm5bq\n"
+            + "wQIDAQAB\n"
+            + "-----END PUBLIC KEY-----\n";
+    BufferedReader reader = new BufferedReader(new StringReader(pem));
+    Key key = PemKeyType.RSA_PSS_2048_SHA256.readKey(reader);
+    assertThat(key).isNotNull();
+    assertThat(key).isInstanceOf(RSAPublicKey.class);
+    RSAPublicKey rsa = (RSAPublicKey) key;
+    assertThat(rsa.getModulus()).isNotNull();
+    assertThat(rsa.getPublicExponent()).isNotNull();
+  }
+
+  @Test
+  public void readKey_withCommentHeaderOutsideMarkers_shouldWork() throws Exception {
+    String pem =
+        "-----BEGIN PUBLIC KEY-----\n"
+            + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv90Xf/NN1lRGBofJQzJf\n"
+            + "lHvo6GAf25GGQGaMmD9T1ZP71CCbJ69lGIS/6akFBg6ECEHGM2EZ4WFLCdr5byUq\n"
+            + "GCf4mY4WuOn+AcwzwAoDz9ASIFcQOoPclO7JYdfo2SOaumumdb5S/7FkKJ70TGYW\n"
+            + "j9aTOYWsCcaojbjGDY/JEXz3BSRIngcgOvXBmV1JokcJ/LsrJD263WE9iUknZDhB\n"
+            + "K7y4ChjHNqL8yJcw/D8xLNiJtIyuxiZ00p/lOVUInr8C/a2C1UGCgEGuXZAEGAdO\n"
+            + "NVez52n5TLvQP3hRd4MTi7YvfhezRcA4aXyIDOv+TYi4p+OVTYQ+FMbkgoWBm5bq\n"
+            + "wQIDAQAB\n"
+            + "-----END PUBLIC KEY-----\n"
+            + "Version: 1.0.0\n";
+    BufferedReader reader = new BufferedReader(new StringReader(pem));
+    Key key = PemKeyType.RSA_PSS_2048_SHA256.readKey(reader);
+    assertThat(key).isNotNull();
+    assertThat(key).isInstanceOf(RSAPublicKey.class);
+    RSAPublicKey rsa = (RSAPublicKey) key;
+    assertThat(rsa.getModulus()).isNotNull();
+    assertThat(rsa.getPublicExponent()).isNotNull();
+  }
+
+  @Test
+  public void readKey_withBEGIN_RSA_PUBLIC_KEY_shouldWork() throws Exception {
+    String pem =
+        "-----BEGIN RSA PUBLIC KEY-----\n"
+            + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv90Xf/NN1lRGBofJQzJf\n"
+            + "lHvo6GAf25GGQGaMmD9T1ZP71CCbJ69lGIS/6akFBg6ECEHGM2EZ4WFLCdr5byUq\n"
+            + "GCf4mY4WuOn+AcwzwAoDz9ASIFcQOoPclO7JYdfo2SOaumumdb5S/7FkKJ70TGYW\n"
+            + "j9aTOYWsCcaojbjGDY/JEXz3BSRIngcgOvXBmV1JokcJ/LsrJD263WE9iUknZDhB\n"
+            + "K7y4ChjHNqL8yJcw/D8xLNiJtIyuxiZ00p/lOVUInr8C/a2C1UGCgEGuXZAEGAdO\n"
+            + "NVez52n5TLvQP3hRd4MTi7YvfhezRcA4aXyIDOv+TYi4p+OVTYQ+FMbkgoWBm5bq\n"
+            + "wQIDAQAB\n"
+            + "-----END RSA PUBLIC KEY-----\n";
+    BufferedReader reader = new BufferedReader(new StringReader(pem));
+    Key key = PemKeyType.RSA_PSS_2048_SHA256.readKey(reader);
+    assertThat(key).isNotNull();
+    assertThat(key).isInstanceOf(RSAPublicKey.class);
+    RSAPublicKey rsa = (RSAPublicKey) key;
+    assertThat(rsa.getModulus()).isNotNull();
+    assertThat(rsa.getPublicExponent()).isNotNull();
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java
index 2c4de17a5b5da236425057cb6ecbeddec293c7f2..69d621e225d3d07046fd9d1a91bd88200e6edebf 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java
@@ -48,7 +48,7 @@ public class RsaSsaPkcs1SignJceTest {
       fail("Unsafe hash, should have thrown exception.");
     } catch (GeneralSecurityException e) {
       // Expected.
-      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+      TestUtil.assertExceptionContains(e, "Unsupported hash: SHA1");
     }
   }
 
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJceTest.java
index 2ba79599e54bf47aa889ea58590b215d88de7862..a3df816d382e1be41be7303059ba6103c441f138 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJceTest.java
@@ -48,7 +48,7 @@ public class RsaSsaPkcs1VerifyJceTest {
       fail("Unsafe hash, should have thrown exception.");
     } catch (GeneralSecurityException e) {
       // Expected.
-      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+      TestUtil.assertExceptionContains(e, "Unsupported hash: SHA1");
     }
   }
 
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssSignJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssSignJceTest.java
index 6a9f1fa8925ff2582d9358f61268d3ef4a940352..b22612dcb35490ee77fb441e910a6abacca4d670 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssSignJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssSignJceTest.java
@@ -47,7 +47,7 @@ public class RsaSsaPssSignJceTest {
       fail("Unsafe hash, should have thrown exception.");
     } catch (GeneralSecurityException e) {
       // Expected.
-      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+      TestUtil.assertExceptionContains(e, "Unsupported hash: SHA1");
     }
   }
 
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJceTest.java
index cfb6789a0e90a718e75070a5be6d6a9068b15d55..ed15e285e1597e2e9a6e2490609fa99fe2ba04e6 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJceTest.java
@@ -47,7 +47,7 @@ public class RsaSsaPssVerifyJceTest {
       fail("Unsafe hash, should have thrown exception.");
     } catch (GeneralSecurityException e) {
       // Expected.
-      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+      TestUtil.assertExceptionContains(e, "Unsupported hash: SHA1");
     }
   }
 
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/ValidatorsTest.java b/java/src/test/java/com/google/crypto/tink/subtle/ValidatorsTest.java
index 16d69a82c6220ea8b0f26bb5710a360cc8cc8e15..96b6d294289b9ba61f674f7303b9476f1d6a5a33 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/ValidatorsTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/ValidatorsTest.java
@@ -142,7 +142,7 @@ public class ValidatorsTest {
       Validators.validateSignatureHash(HashType.SHA1);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+      TestUtil.assertExceptionContains(e, "Unsupported hash: SHA1");
     }
   }
 
diff --git a/proto/common.proto b/proto/common.proto
index 393e415f6f64303e21f14943d577ce49cbc56134..41e1b6df13e7a48d257bd6b455cbbb4033fc3b3d 100644
--- a/proto/common.proto
+++ b/proto/common.proto
@@ -43,6 +43,7 @@ enum EcPointFormat {
 enum HashType {
   UNKNOWN_HASH = 0;
   SHA1 = 1; // Using SHA1 for digital signature is deprecated but HMAC-SHA1 is fine.
+  SHA384 = 2;
   SHA256 = 3;
   SHA512 = 4;
 };