Revert "Remove code supporting the SDES crypto mode in SDP"

This reverts commit ee212a72f220641f0a4a23fb2c1bd600a9069440.

Reason for revert: Don't remove until downstream issues resolved

Original change's description:
> Remove code supporting the SDES crypto mode in SDP
>
> Removes the ability to accept nonencrypted answers to encrypted offers.
> Fixes some logic around bundled sessions and requirement for
> transport parameters.
>
> Bug: webrtc:11066
> Change-Id: I56d8628d223614918a1e5260fdb8a117c8c02dbd
> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/236344
> Commit-Queue: Harald Alvestrand <hta@webrtc.org>
> Reviewed-by: Niels Moller <nisse@webrtc.org>
> Cr-Commit-Position: refs/heads/main@{#35298}

# Not skipping CQ checks because original CL landed > 1 day ago.

Bug: webrtc:11066
Change-Id: I0c400ceffe1b08e0be7b44abbb54c8a032128f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/237223
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35312}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 214600e..0352f99 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -133,6 +133,7 @@
   sources = [
     "candidate.cc",
     "candidate.h",
+    "crypto_params.h",
     "data_channel_interface.cc",
     "data_channel_interface.h",
     "dtls_transport_interface.cc",
diff --git a/api/crypto_params.h b/api/crypto_params.h
new file mode 100644
index 0000000..5da352c
--- /dev/null
+++ b/api/crypto_params.h
@@ -0,0 +1,41 @@
+/*
+ *  Copyright (c) 2004 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_CRYPTO_PARAMS_H_
+#define API_CRYPTO_PARAMS_H_
+
+#include <string>
+
+namespace cricket {
+
+// Parameters for SRTP negotiation, as described in RFC 4568.
+// TODO(benwright) - Rename to SrtpCryptoParams as these only apply to SRTP and
+// not generic crypto parameters for WebRTC.
+struct CryptoParams {
+  CryptoParams() : tag(0) {}
+  CryptoParams(int t,
+               const std::string& cs,
+               const std::string& kp,
+               const std::string& sp)
+      : tag(t), cipher_suite(cs), key_params(kp), session_params(sp) {}
+
+  bool Matches(const CryptoParams& params) const {
+    return (tag == params.tag && cipher_suite == params.cipher_suite);
+  }
+
+  int tag;
+  std::string cipher_suite;
+  std::string key_params;
+  std::string session_params;
+};
+
+}  // namespace cricket
+
+#endif  // API_CRYPTO_PARAMS_H_
diff --git a/p2p/base/transport_description.h b/p2p/base/transport_description.h
index 340edc2..32fdb5c 100644
--- a/p2p/base/transport_description.h
+++ b/p2p/base/transport_description.h
@@ -24,6 +24,17 @@
 
 namespace cricket {
 
+// SEC_ENABLED and SEC_REQUIRED should only be used if the session
+// was negotiated over TLS, to protect the inline crypto material
+// exchange.
+// SEC_DISABLED: No crypto in outgoing offer, ignore any supplied crypto.
+// SEC_ENABLED:  Crypto in outgoing offer and answer (if supplied in offer).
+// SEC_REQUIRED: Crypto in outgoing offer and answer. Fail any offer with absent
+//               or unsupported crypto.
+// TODO(deadbeef): Remove this or rename it to something more appropriate, like
+// SdesPolicy.
+enum SecurePolicy { SEC_DISABLED, SEC_ENABLED, SEC_REQUIRED };
+
 // Whether our side of the call is driving the negotiation, or the other side.
 enum IceRole { ICEROLE_CONTROLLING = 0, ICEROLE_CONTROLLED, ICEROLE_UNKNOWN };
 
diff --git a/p2p/base/transport_description_factory.cc b/p2p/base/transport_description_factory.cc
index 5917713..6beae34 100644
--- a/p2p/base/transport_description_factory.cc
+++ b/p2p/base/transport_description_factory.cc
@@ -21,7 +21,8 @@
 
 namespace cricket {
 
-TransportDescriptionFactory::TransportDescriptionFactory() {}
+TransportDescriptionFactory::TransportDescriptionFactory()
+    : secure_(SEC_DISABLED) {}
 
 TransportDescriptionFactory::~TransportDescriptionFactory() = default;
 
@@ -46,7 +47,7 @@
   }
 
   // If we are trying to establish a secure transport, add a fingerprint.
-  if (IsEncrypted()) {
+  if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
     // Fail if we can't create the fingerprint.
     // If we are the initiator set role to "actpass".
     if (!SetSecurityInfo(desc.get(), CONNECTIONROLE_ACTPASS)) {
@@ -89,7 +90,7 @@
   // Negotiate security params.
   if (offer && offer->identity_fingerprint.get()) {
     // The offer supports DTLS, so answer with DTLS, as long as we support it.
-    if (IsEncrypted()) {
+    if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
       ConnectionRole role = CONNECTIONROLE_NONE;
       // If the offer does not constrain the role, go with preference.
       if (offer->connection_role == CONNECTIONROLE_ACTPASS) {
@@ -115,7 +116,7 @@
         return NULL;
       }
     }
-  } else if (require_transport_attributes && IsEncrypted()) {
+  } else if (require_transport_attributes && secure_ == SEC_REQUIRED) {
     // We require DTLS, but the other side didn't offer it. Fail.
     RTC_LOG(LS_WARNING) << "Failed to create TransportDescription answer "
                            "because of incompatible security settings";
diff --git a/p2p/base/transport_description_factory.h b/p2p/base/transport_description_factory.h
index 3e999a6..0be7f32 100644
--- a/p2p/base/transport_description_factory.h
+++ b/p2p/base/transport_description_factory.h
@@ -40,20 +40,20 @@
   TransportDescriptionFactory();
   ~TransportDescriptionFactory();
 
+  SecurePolicy secure() const { return secure_; }
   // The certificate to use when setting up DTLS.
   const rtc::scoped_refptr<rtc::RTCCertificate>& certificate() const {
     return certificate_;
   }
 
-  // Specifies the certificate to use.
-  // When a certificate is set, transport will be encrypted.
+  // Specifies the transport security policy to use.
+  void set_secure(SecurePolicy s) { secure_ = s; }
+  // Specifies the certificate to use (only used when secure != SEC_DISABLED).
   void set_certificate(
       const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
     certificate_ = certificate;
   }
 
-  bool IsEncrypted() const { return certificate_ != nullptr; }
-
   // Creates a transport description suitable for use in an offer.
   std::unique_ptr<TransportDescription> CreateOffer(
       const TransportOptions& options,
@@ -77,6 +77,7 @@
   bool SetSecurityInfo(TransportDescription* description,
                        ConnectionRole role) const;
 
+  SecurePolicy secure_;
   rtc::scoped_refptr<rtc::RTCCertificate> certificate_;
 };
 
diff --git a/p2p/base/transport_description_factory_unittest.cc b/p2p/base/transport_description_factory_unittest.cc
index 45bd61f..77a56ef 100644
--- a/p2p/base/transport_description_factory_unittest.cc
+++ b/p2p/base/transport_description_factory_unittest.cc
@@ -144,19 +144,17 @@
   }
 
  protected:
-  void SetDtls(bool f1_dtls, bool f2_dtls) {
-    if (f1_dtls) {
+  void SetDtls(bool dtls) {
+    if (dtls) {
+      f1_.set_secure(cricket::SEC_ENABLED);
+      f2_.set_secure(cricket::SEC_ENABLED);
       f1_.set_certificate(cert1_);
-    } else {
-      f1_.set_certificate(nullptr);
-    }
-    if (f2_dtls) {
       f2_.set_certificate(cert2_);
     } else {
-      f2_.set_certificate(nullptr);
+      f1_.set_secure(cricket::SEC_DISABLED);
+      f2_.set_secure(cricket::SEC_DISABLED);
     }
   }
-  void SetDtls(bool dtls) { SetDtls(dtls, dtls); }
 
   cricket::IceCredentialsIterator ice_credentials_;
   TransportDescriptionFactory f1_;
@@ -173,19 +171,33 @@
 }
 
 TEST_F(TransportDescriptionFactoryTest, TestOfferDtls) {
-  SetDtls(true);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_certificate(cert1_);
   std::string digest_alg;
   ASSERT_TRUE(
       cert1_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg));
   std::unique_ptr<TransportDescription> desc =
       f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
   CheckDesc(desc.get(), "", "", "", digest_alg);
+  // Ensure it also works with SEC_REQUIRED.
+  f1_.set_secure(cricket::SEC_REQUIRED);
+  desc = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+  CheckDesc(desc.get(), "", "", "", digest_alg);
+}
+
+// Test generating an offer with DTLS fails with no identity.
+TEST_F(TransportDescriptionFactoryTest, TestOfferDtlsWithNoIdentity) {
+  f1_.set_secure(cricket::SEC_ENABLED);
+  std::unique_ptr<TransportDescription> desc =
+      f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+  ASSERT_TRUE(desc.get() == NULL);
 }
 
 // Test updating an offer with DTLS to pick ICE.
 // The ICE credentials should stay the same in the new offer.
 TEST_F(TransportDescriptionFactoryTest, TestOfferDtlsReofferDtls) {
-  SetDtls(true);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_certificate(cert1_);
   std::string digest_alg;
   ASSERT_TRUE(
       cert1_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg));
@@ -225,7 +237,8 @@
 
 // Test that we handle answering an offer with DTLS with no DTLS.
 TEST_F(TransportDescriptionFactoryTest, TestAnswerDtlsToNoDtls) {
-  SetDtls(true, false);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_certificate(cert1_);
   std::unique_ptr<TransportDescription> offer =
       f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
   ASSERT_TRUE(offer.get() != NULL);
@@ -234,20 +247,31 @@
   CheckDesc(desc.get(), "", "", "", "");
 }
 
-// Test that we reject answering an offer without DTLS if we have DTLS enabled.
+// Test that we handle answering an offer without DTLS if we have DTLS enabled,
+// but fail if we require DTLS.
 TEST_F(TransportDescriptionFactoryTest, TestAnswerNoDtlsToDtls) {
-  SetDtls(false, true);
+  f2_.set_secure(cricket::SEC_ENABLED);
+  f2_.set_certificate(cert2_);
   std::unique_ptr<TransportDescription> offer =
       f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<TransportDescription> desc = f2_.CreateAnswer(
       offer.get(), TransportOptions(), true, NULL, &ice_credentials_);
-  ASSERT_FALSE(desc);
+  CheckDesc(desc.get(), "", "", "", "");
+  f2_.set_secure(cricket::SEC_REQUIRED);
+  desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL,
+                          &ice_credentials_);
+  ASSERT_TRUE(desc.get() == NULL);
 }
 
-// Test that we handle answering an DTLS offer with DTLS.
+// Test that we handle answering an DTLS offer with DTLS, both if we have
+// DTLS enabled and required.
 TEST_F(TransportDescriptionFactoryTest, TestAnswerDtlsToDtls) {
-  SetDtls(true, true);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_certificate(cert1_);
+
+  f2_.set_secure(cricket::SEC_ENABLED);
+  f2_.set_certificate(cert2_);
   // f2_ produces the answer that is being checked in this test, so the
   // answer must contain fingerprint lines with cert2_'s digest algorithm.
   std::string digest_alg2;
@@ -260,6 +284,10 @@
   std::unique_ptr<TransportDescription> desc = f2_.CreateAnswer(
       offer.get(), TransportOptions(), true, NULL, &ice_credentials_);
   CheckDesc(desc.get(), "", "", "", digest_alg2);
+  f2_.set_secure(cricket::SEC_REQUIRED);
+  desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL,
+                          &ice_credentials_);
+  CheckDesc(desc.get(), "", "", "", digest_alg2);
 }
 
 // Test that ice ufrag and password is changed in an updated offer and answer
@@ -326,7 +354,11 @@
 }
 
 TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsActpassOffer) {
-  SetDtls(true);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_certificate(cert1_);
+
+  f2_.set_secure(cricket::SEC_ENABLED);
+  f2_.set_certificate(cert2_);
   cricket::TransportOptions options;
   std::unique_ptr<TransportDescription> offer =
       f1_.CreateOffer(options, nullptr, &ice_credentials_);
@@ -337,7 +369,11 @@
 }
 
 TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsActiveOffer) {
-  SetDtls(true);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_certificate(cert1_);
+
+  f2_.set_secure(cricket::SEC_ENABLED);
+  f2_.set_certificate(cert2_);
   cricket::TransportOptions options;
   std::unique_ptr<TransportDescription> offer =
       f1_.CreateOffer(options, nullptr, &ice_credentials_);
@@ -349,7 +385,11 @@
 }
 
 TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsPassiveOffer) {
-  SetDtls(true);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_certificate(cert1_);
+
+  f2_.set_secure(cricket::SEC_ENABLED);
+  f2_.set_certificate(cert2_);
   cricket::TransportOptions options;
   std::unique_ptr<TransportDescription> offer =
       f1_.CreateOffer(options, nullptr, &ice_credentials_);
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 1e8187ad..f31109a 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -81,6 +81,8 @@
     "sctp_transport.h",
     "sctp_utils.cc",
     "sctp_utils.h",
+    "srtp_filter.cc",
+    "srtp_filter.h",
     "srtp_session.cc",
     "srtp_session.h",
     "srtp_transport.cc",
@@ -884,6 +886,7 @@
       "rtp_transport_unittest.cc",
       "sctp_transport_unittest.cc",
       "session_description_unittest.cc",
+      "srtp_filter_unittest.cc",
       "srtp_session_unittest.cc",
       "srtp_transport_unittest.cc",
       "test/rtp_transport_test_util.h",
diff --git a/pc/channel.h b/pc/channel.h
index 19e07fa..4628c86 100644
--- a/pc/channel.h
+++ b/pc/channel.h
@@ -47,6 +47,7 @@
 #include "pc/rtp_transport.h"
 #include "pc/rtp_transport_internal.h"
 #include "pc/session_description.h"
+#include "pc/srtp_filter.h"
 #include "pc/srtp_transport.h"
 #include "rtc_base/async_packet_socket.h"
 #include "rtc_base/async_udp_socket.h"
diff --git a/pc/dtls_srtp_transport.h b/pc/dtls_srtp_transport.h
index 6daaf27..da068c9 100644
--- a/pc/dtls_srtp_transport.h
+++ b/pc/dtls_srtp_transport.h
@@ -15,6 +15,7 @@
 #include <vector>
 
 #include "absl/types/optional.h"
+#include "api/crypto_params.h"
 #include "api/dtls_transport_interface.h"
 #include "api/rtc_error.h"
 #include "p2p/base/dtls_transport_internal.h"
@@ -48,6 +49,15 @@
 
   void SetOnDtlsStateChange(std::function<void(void)> callback);
 
+  RTCError SetSrtpSendKey(const cricket::CryptoParams& params) override {
+    return RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
+                    "Set SRTP keys for DTLS-SRTP is not supported.");
+  }
+  RTCError SetSrtpReceiveKey(const cricket::CryptoParams& params) override {
+    return RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
+                    "Set SRTP keys for DTLS-SRTP is not supported.");
+  }
+
   // If `active_reset_srtp_params_` is set to be true, the SRTP parameters will
   // be reset whenever the DtlsTransports are reset.
   void SetActiveResetSrtpParams(bool active_reset_srtp_params) {
diff --git a/pc/jsep_transport.cc b/pc/jsep_transport.cc
index e40c7b5..a94af42 100644
--- a/pc/jsep_transport.cc
+++ b/pc/jsep_transport.cc
@@ -37,10 +37,12 @@
 
 JsepTransportDescription::JsepTransportDescription(
     bool rtcp_mux_enabled,
+    const std::vector<CryptoParams>& cryptos,
     const std::vector<int>& encrypted_header_extension_ids,
     int rtp_abs_sendtime_extn_id,
     const TransportDescription& transport_desc)
     : rtcp_mux_enabled(rtcp_mux_enabled),
+      cryptos(cryptos),
       encrypted_header_extension_ids(encrypted_header_extension_ids),
       rtp_abs_sendtime_extn_id(rtp_abs_sendtime_extn_id),
       transport_desc(transport_desc) {}
@@ -48,6 +50,7 @@
 JsepTransportDescription::JsepTransportDescription(
     const JsepTransportDescription& from)
     : rtcp_mux_enabled(from.rtcp_mux_enabled),
+      cryptos(from.cryptos),
       encrypted_header_extension_ids(from.encrypted_header_extension_ids),
       rtp_abs_sendtime_extn_id(from.rtp_abs_sendtime_extn_id),
       transport_desc(from.transport_desc) {}
@@ -60,6 +63,7 @@
     return *this;
   }
   rtcp_mux_enabled = from.rtcp_mux_enabled;
+  cryptos = from.cryptos;
   encrypted_header_extension_ids = from.encrypted_header_extension_ids;
   rtp_abs_sendtime_extn_id = from.rtp_abs_sendtime_extn_id;
   transport_desc = from.transport_desc;
@@ -85,6 +89,7 @@
       ice_transport_(std::move(ice_transport)),
       rtcp_ice_transport_(std::move(rtcp_ice_transport)),
       unencrypted_rtp_transport_(std::move(unencrypted_rtp_transport)),
+      sdes_transport_(std::move(sdes_transport)),
       dtls_srtp_transport_(std::move(dtls_srtp_transport)),
       rtp_dtls_transport_(rtp_dtls_transport
                               ? rtc::make_ref_counted<webrtc::DtlsTransport>(
@@ -112,10 +117,15 @@
                 (rtcp_dtls_transport_ != nullptr));
   // Verify the "only one out of these three can be set" invariant.
   if (unencrypted_rtp_transport_) {
+    RTC_DCHECK(!sdes_transport);
+    RTC_DCHECK(!dtls_srtp_transport);
+  } else if (sdes_transport_) {
+    RTC_DCHECK(!unencrypted_rtp_transport);
     RTC_DCHECK(!dtls_srtp_transport);
   } else {
     RTC_DCHECK(dtls_srtp_transport_);
     RTC_DCHECK(!unencrypted_rtp_transport);
+    RTC_DCHECK(!sdes_transport);
   }
 
   if (sctp_transport_) {
@@ -162,8 +172,19 @@
                             "Failed to setup RTCP mux.");
   }
 
-  if (dtls_srtp_transport_) {
+  // If doing SDES, setup the SDES crypto parameters.
+  if (sdes_transport_) {
     RTC_DCHECK(!unencrypted_rtp_transport_);
+    RTC_DCHECK(!dtls_srtp_transport_);
+    if (!SetSdes(jsep_description.cryptos,
+                 jsep_description.encrypted_header_extension_ids, type,
+                 ContentSource::CS_LOCAL)) {
+      return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
+                              "Failed to setup SDES crypto parameters.");
+    }
+  } else if (dtls_srtp_transport_) {
+    RTC_DCHECK(!unencrypted_rtp_transport_);
+    RTC_DCHECK(!sdes_transport_);
     dtls_srtp_transport_->UpdateRecvEncryptedHeaderExtensionIds(
         jsep_description.encrypted_header_extension_ids);
   }
@@ -240,8 +261,21 @@
                             "Failed to setup RTCP mux.");
   }
 
-  if (dtls_srtp_transport_) {
+  // If doing SDES, setup the SDES crypto parameters.
+  if (sdes_transport_) {
     RTC_DCHECK(!unencrypted_rtp_transport_);
+    RTC_DCHECK(!dtls_srtp_transport_);
+    if (!SetSdes(jsep_description.cryptos,
+                 jsep_description.encrypted_header_extension_ids, type,
+                 ContentSource::CS_REMOTE)) {
+      return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
+                              "Failed to setup SDES crypto parameters.");
+    }
+    sdes_transport_->CacheRtpAbsSendTimeHeaderExtension(
+        jsep_description.rtp_abs_sendtime_extn_id);
+  } else if (dtls_srtp_transport_) {
+    RTC_DCHECK(!unencrypted_rtp_transport_);
+    RTC_DCHECK(!sdes_transport_);
     dtls_srtp_transport_->UpdateSendEncryptedHeaderExtensionIds(
         jsep_description.encrypted_header_extension_ids);
     dtls_srtp_transport_->CacheRtpAbsSendTimeHeaderExtension(
@@ -440,11 +474,17 @@
 
 void JsepTransport::ActivateRtcpMux() {
   if (unencrypted_rtp_transport_) {
+    RTC_DCHECK(!sdes_transport_);
     RTC_DCHECK(!dtls_srtp_transport_);
     unencrypted_rtp_transport_->SetRtcpPacketTransport(nullptr);
+  } else if (sdes_transport_) {
+    RTC_DCHECK(!unencrypted_rtp_transport_);
+    RTC_DCHECK(!dtls_srtp_transport_);
+    sdes_transport_->SetRtcpPacketTransport(nullptr);
   } else if (dtls_srtp_transport_) {
     RTC_DCHECK(dtls_srtp_transport_);
     RTC_DCHECK(!unencrypted_rtp_transport_);
+    RTC_DCHECK(!sdes_transport_);
     dtls_srtp_transport_->SetDtlsTransports(rtp_dtls_transport(),
                                             /*rtcp_dtls_transport=*/nullptr);
   }
@@ -453,6 +493,51 @@
   rtcp_mux_active_callback_();
 }
 
+bool JsepTransport::SetSdes(const std::vector<CryptoParams>& cryptos,
+                            const std::vector<int>& encrypted_extension_ids,
+                            webrtc::SdpType type,
+                            ContentSource source) {
+  RTC_DCHECK_RUN_ON(network_thread_);
+  bool ret = false;
+  ret = sdes_negotiator_.Process(cryptos, type, source);
+  if (!ret) {
+    return ret;
+  }
+
+  if (source == ContentSource::CS_LOCAL) {
+    recv_extension_ids_ = encrypted_extension_ids;
+  } else {
+    send_extension_ids_ = encrypted_extension_ids;
+  }
+
+  // If setting an SDES answer succeeded, apply the negotiated parameters
+  // to the SRTP transport.
+  if ((type == SdpType::kPrAnswer || type == SdpType::kAnswer) && ret) {
+    if (sdes_negotiator_.send_cipher_suite() &&
+        sdes_negotiator_.recv_cipher_suite()) {
+      RTC_DCHECK(send_extension_ids_);
+      RTC_DCHECK(recv_extension_ids_);
+      ret = sdes_transport_->SetRtpParams(
+          *(sdes_negotiator_.send_cipher_suite()),
+          sdes_negotiator_.send_key().data(),
+          static_cast<int>(sdes_negotiator_.send_key().size()),
+          *(send_extension_ids_), *(sdes_negotiator_.recv_cipher_suite()),
+          sdes_negotiator_.recv_key().data(),
+          static_cast<int>(sdes_negotiator_.recv_key().size()),
+          *(recv_extension_ids_));
+    } else {
+      RTC_LOG(LS_INFO) << "No crypto keys are provided for SDES.";
+      if (type == SdpType::kAnswer) {
+        // Explicitly reset the `sdes_transport_` if no crypto param is
+        // provided in the answer. No need to call `ResetParams()` for
+        // `sdes_negotiator_` because it resets the params inside `SetAnswer`.
+        sdes_transport_->ResetParams();
+      }
+    }
+  }
+  return ret;
+}
+
 webrtc::RTCError JsepTransport::NegotiateAndSetDtlsParameters(
     SdpType local_description_type) {
   RTC_DCHECK_RUN_ON(network_thread_);
diff --git a/pc/jsep_transport.h b/pc/jsep_transport.h
index db63aae..e3e929b 100644
--- a/pc/jsep_transport.h
+++ b/pc/jsep_transport.h
@@ -19,6 +19,7 @@
 
 #include "absl/types/optional.h"
 #include "api/candidate.h"
+#include "api/crypto_params.h"
 #include "api/ice_transport_interface.h"
 #include "api/jsep.h"
 #include "api/rtc_error.h"
@@ -39,6 +40,8 @@
 #include "pc/rtp_transport_internal.h"
 #include "pc/sctp_transport.h"
 #include "pc/session_description.h"
+#include "pc/srtp_filter.h"
+#include "pc/srtp_transport.h"
 #include "pc/transport_stats.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/constructor_magic.h"
@@ -57,6 +60,7 @@
   JsepTransportDescription();
   JsepTransportDescription(
       bool rtcp_mux_enabled,
+      const std::vector<CryptoParams>& cryptos,
       const std::vector<int>& encrypted_header_extension_ids,
       int rtp_abs_sendtime_extn_id,
       const TransportDescription& transport_description);
@@ -66,6 +70,7 @@
   JsepTransportDescription& operator=(const JsepTransportDescription& from);
 
   bool rtcp_mux_enabled = true;
+  std::vector<CryptoParams> cryptos;
   std::vector<int> encrypted_header_extension_ids;
   int rtp_abs_sendtime_extn_id = -1;
   // TODO(zhihuang): Add the ICE and DTLS related variables and methods from
@@ -166,6 +171,9 @@
     if (dtls_srtp_transport_) {
       return dtls_srtp_transport_.get();
     }
+    if (sdes_transport_) {
+      return sdes_transport_.get();
+    }
     if (unencrypted_rtp_transport_) {
       return unencrypted_rtp_transport_.get();
     }
@@ -236,6 +244,11 @@
 
   void ActivateRtcpMux() RTC_RUN_ON(network_thread_);
 
+  bool SetSdes(const std::vector<CryptoParams>& cryptos,
+               const std::vector<int>& encrypted_extension_ids,
+               webrtc::SdpType type,
+               ContentSource source);
+
   // Negotiates and sets the DTLS parameters based on the current local and
   // remote transport description, such as the DTLS role to use, and whether
   // DTLS should be activated.
@@ -287,6 +300,7 @@
   // To avoid downcasting and make it type safe, keep three unique pointers for
   // different SRTP mode and only one of these is non-nullptr.
   const std::unique_ptr<webrtc::RtpTransport> unencrypted_rtp_transport_;
+  const std::unique_ptr<webrtc::SrtpTransport> sdes_transport_;
   const std::unique_ptr<webrtc::DtlsSrtpTransport> dtls_srtp_transport_;
 
   const rtc::scoped_refptr<webrtc::DtlsTransport> rtp_dtls_transport_;
@@ -299,6 +313,7 @@
       sctp_data_channel_transport_;
   const rtc::scoped_refptr<webrtc::SctpTransport> sctp_transport_;
 
+  SrtpFilter sdes_negotiator_ RTC_GUARDED_BY(network_thread_);
   RtcpMuxFilter rtcp_mux_negotiator_ RTC_GUARDED_BY(network_thread_);
 
   // Cache the encrypted header extension IDs for SDES negoitation.
diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc
index 24154fd..b1fccbe 100644
--- a/pc/jsep_transport_controller.cc
+++ b/pc/jsep_transport_controller.cc
@@ -918,8 +918,8 @@
                               : content_desc->rtcp_mux();
 
   return cricket::JsepTransportDescription(
-      rtcp_mux_enabled, encrypted_extension_ids, rtp_abs_sendtime_extn_id,
-      transport_info.description);
+      rtcp_mux_enabled, content_desc->cryptos(), encrypted_extension_ids,
+      rtp_abs_sendtime_extn_id, transport_info.description);
 }
 
 std::vector<int> JsepTransportController::GetEncryptedHeaderExtensionIds(
@@ -1017,6 +1017,12 @@
   if (transport) {
     return RTCError::OK();
   }
+  const cricket::MediaContentDescription* content_desc =
+      content_info.media_description();
+  if (certificate_ && !content_desc->cryptos().empty()) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "SDES and DTLS-SRTP cannot be enabled at the same time.");
+  }
 
   rtc::scoped_refptr<webrtc::IceTransportInterface> ice =
       CreateIceTransport(content_info.name, /*rtcp=*/false);
@@ -1044,6 +1050,10 @@
         << "Creating UnencryptedRtpTransport, becayse encryption is disabled.";
     unencrypted_rtp_transport = CreateUnencryptedRtpTransport(
         content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
+  } else if (!content_desc->cryptos().empty()) {
+    sdes_transport = CreateSdesTransport(
+        content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
+    RTC_LOG(LS_INFO) << "Creating SdesTransport.";
   } else {
     RTC_LOG(LS_INFO) << "Creating DtlsSrtpTransport.";
     dtls_srtp_transport = CreateDtlsSrtpTransport(
diff --git a/pc/jsep_transport_unittest.cc b/pc/jsep_transport_unittest.cc
index d9200d9..41c9bab 100644
--- a/pc/jsep_transport_unittest.cc
+++ b/pc/jsep_transport_unittest.cc
@@ -30,6 +30,11 @@
 static const char kIcePwd2[] = "TESTIEPWD00000000000002";
 static const char kTransportName[] = "Test Transport";
 
+enum class SrtpMode {
+  kSdes,
+  kDtlsSrtp,
+};
+
 struct NegotiateRoleParams {
   ConnectionRole local_role;
   ConnectionRole remote_role;
@@ -87,7 +92,8 @@
 
   // Create a new JsepTransport with a FakeDtlsTransport and a
   // FakeIceTransport.
-  std::unique_ptr<JsepTransport> CreateJsepTransport2(bool rtcp_mux_enabled) {
+  std::unique_ptr<JsepTransport> CreateJsepTransport2(bool rtcp_mux_enabled,
+                                                      SrtpMode srtp_mode) {
     auto ice_internal = std::make_unique<FakeIceTransport>(
         kTransportName, ICE_CANDIDATE_COMPONENT_RTP);
     auto rtp_dtls_transport =
@@ -107,8 +113,19 @@
     std::unique_ptr<webrtc::RtpTransport> unencrypted_rtp_transport;
     std::unique_ptr<webrtc::SrtpTransport> sdes_transport;
     std::unique_ptr<webrtc::DtlsSrtpTransport> dtls_srtp_transport;
-    dtls_srtp_transport = CreateDtlsSrtpTransport(rtp_dtls_transport.get(),
-                                                  rtcp_dtls_transport.get());
+    switch (srtp_mode) {
+      case SrtpMode::kSdes:
+        sdes_transport = CreateSdesTransport(rtp_dtls_transport.get(),
+                                             rtcp_dtls_transport.get());
+        sdes_transport_ = sdes_transport.get();
+        break;
+      case SrtpMode::kDtlsSrtp:
+        dtls_srtp_transport = CreateDtlsSrtpTransport(
+            rtp_dtls_transport.get(), rtcp_dtls_transport.get());
+        break;
+      default:
+        RTC_NOTREACHED();
+    }
 
     auto jsep_transport = std::make_unique<JsepTransport>(
         kTransportName, /*local_certificate=*/nullptr, std::move(ice),
@@ -167,7 +184,7 @@
 // This test verifies the ICE parameters are properly applied to the transports.
 TEST_P(JsepTransport2WithRtcpMux, SetIceParameters) {
   bool rtcp_mux_enabled = GetParam();
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
 
   JsepTransportDescription jsep_description;
   jsep_description.transport_desc = TransportDescription(kIceUfrag1, kIcePwd1);
@@ -213,7 +230,7 @@
 // Similarly, test DTLS parameters are properly applied to the transports.
 TEST_P(JsepTransport2WithRtcpMux, SetDtlsParameters) {
   bool rtcp_mux_enabled = GetParam();
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
 
   // Create certificates.
   rtc::scoped_refptr<rtc::RTCCertificate> local_cert =
@@ -264,7 +281,7 @@
 // CONNECTIONROLE_PASSIVE, expecting SSL_CLIENT role.
 TEST_P(JsepTransport2WithRtcpMux, SetDtlsParametersWithPassiveAnswer) {
   bool rtcp_mux_enabled = GetParam();
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
 
   // Create certificates.
   rtc::scoped_refptr<rtc::RTCCertificate> local_cert =
@@ -316,7 +333,7 @@
 // only starts returning "false" once an ICE restart has been initiated.
 TEST_P(JsepTransport2WithRtcpMux, NeedsIceRestart) {
   bool rtcp_mux_enabled = GetParam();
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
 
   // Use the same JsepTransportDescription for both offer and answer.
   JsepTransportDescription description;
@@ -361,7 +378,7 @@
 
 TEST_P(JsepTransport2WithRtcpMux, GetStats) {
   bool rtcp_mux_enabled = GetParam();
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
 
   size_t expected_stats_size = rtcp_mux_enabled ? 1u : 2u;
   TransportStats stats;
@@ -377,7 +394,7 @@
 // certificate matches the fingerprint.
 TEST_P(JsepTransport2WithRtcpMux, VerifyCertificateFingerprint) {
   bool rtcp_mux_enabled = GetParam();
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
 
   EXPECT_FALSE(
       jsep_transport_->VerifyCertificateFingerprint(nullptr, nullptr).ok());
@@ -451,7 +468,8 @@
   };
 
   for (auto& param : valid_client_params) {
-    jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+    jsep_transport_ =
+        CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
     jsep_transport_->SetLocalCertificate(certificate);
 
     local_description.transport_desc.connection_role = param.local_role;
@@ -496,7 +514,8 @@
   };
 
   for (auto& param : valid_server_params) {
-    jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+    jsep_transport_ =
+        CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
     jsep_transport_->SetLocalCertificate(certificate);
 
     local_description.transport_desc.connection_role = param.local_role;
@@ -567,7 +586,8 @@
        SdpType::kPrAnswer}};
 
   for (auto& param : duplicate_params) {
-    jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+    jsep_transport_ =
+        CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
     jsep_transport_->SetLocalCertificate(certificate);
 
     local_description.transport_desc.connection_role = param.local_role;
@@ -617,7 +637,8 @@
        SdpType::kPrAnswer}};
 
   for (auto& param : offerer_without_actpass_params) {
-    jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+    jsep_transport_ =
+        CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
     jsep_transport_->SetLocalCertificate(certificate);
 
     local_description.transport_desc.connection_role = param.local_role;
@@ -663,7 +684,7 @@
       rtc::RTCCertificate::Create(
           rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA));
   bool rtcp_mux_enabled = true;
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
   jsep_transport_->SetLocalCertificate(certificate);
 
   JsepTransportDescription local_offer =
@@ -710,7 +731,7 @@
       rtc::RTCCertificate::Create(
           rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA));
   bool rtcp_mux_enabled = true;
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
   jsep_transport_->SetLocalCertificate(certificate);
 
   JsepTransportDescription local_offer =
@@ -756,7 +777,7 @@
       rtc::RTCCertificate::Create(
           rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA));
   bool rtcp_mux_enabled = true;
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
   jsep_transport_->SetLocalCertificate(certificate);
 
   JsepTransportDescription remote_desc =
@@ -801,7 +822,7 @@
       rtc::RTCCertificate::Create(
           rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA));
   bool rtcp_mux_enabled = true;
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
   jsep_transport_->SetLocalCertificate(certificate);
 
   JsepTransportDescription remote_desc =
@@ -846,7 +867,7 @@
       rtc::RTCCertificate::Create(
           rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA));
   bool rtcp_mux_enabled = true;
-  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled);
+  jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp);
   jsep_transport_->SetLocalCertificate(certificate);
 
   JsepTransportDescription remote_desc =
@@ -878,7 +899,8 @@
 // Tests that when the RTCP mux is successfully negotiated, the RTCP transport
 // will be destroyed and the SignalRtpMuxActive will be fired.
 TEST_F(JsepTransport2Test, RtcpMuxNegotiation) {
-  jsep_transport_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/false);
+  jsep_transport_ =
+      CreateJsepTransport2(/*rtcp_mux_enabled=*/false, SrtpMode::kDtlsSrtp);
   JsepTransportDescription local_desc;
   local_desc.rtcp_mux_enabled = true;
   ASSERT_NE(nullptr, jsep_transport_->rtcp_dtls_transport());
@@ -900,7 +922,8 @@
   EXPECT_TRUE(signal_rtcp_mux_active_received_);
 
   // The remote side doesn't support RTCP-mux.
-  jsep_transport_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/false);
+  jsep_transport_ =
+      CreateJsepTransport2(/*rtcp_mux_enabled=*/false, SrtpMode::kDtlsSrtp);
   signal_rtcp_mux_active_received_ = false;
   remote_desc.rtcp_mux_enabled = false;
   ASSERT_TRUE(
@@ -916,10 +939,87 @@
   EXPECT_FALSE(signal_rtcp_mux_active_received_);
 }
 
+TEST_F(JsepTransport2Test, SdesNegotiation) {
+  jsep_transport_ =
+      CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kSdes);
+  ASSERT_TRUE(sdes_transport_);
+  EXPECT_FALSE(sdes_transport_->IsSrtpActive());
+
+  JsepTransportDescription offer_desc;
+  offer_desc.cryptos.push_back(cricket::CryptoParams(
+      1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40),
+      std::string()));
+  ASSERT_TRUE(
+      jsep_transport_
+          ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer)
+          .ok());
+
+  JsepTransportDescription answer_desc;
+  answer_desc.cryptos.push_back(cricket::CryptoParams(
+      1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40),
+      std::string()));
+  ASSERT_TRUE(
+      jsep_transport_
+          ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer)
+          .ok());
+  EXPECT_TRUE(sdes_transport_->IsSrtpActive());
+}
+
+TEST_F(JsepTransport2Test, SdesNegotiationWithEmptyCryptosInAnswer) {
+  jsep_transport_ =
+      CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kSdes);
+  ASSERT_TRUE(sdes_transport_);
+  EXPECT_FALSE(sdes_transport_->IsSrtpActive());
+
+  JsepTransportDescription offer_desc;
+  offer_desc.cryptos.push_back(cricket::CryptoParams(
+      1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40),
+      std::string()));
+  ASSERT_TRUE(
+      jsep_transport_
+          ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer)
+          .ok());
+
+  JsepTransportDescription answer_desc;
+  ASSERT_TRUE(
+      jsep_transport_
+          ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer)
+          .ok());
+  // SRTP is not active because the crypto parameter is answer is empty.
+  EXPECT_FALSE(sdes_transport_->IsSrtpActive());
+}
+
+TEST_F(JsepTransport2Test, SdesNegotiationWithMismatchedCryptos) {
+  jsep_transport_ =
+      CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kSdes);
+  ASSERT_TRUE(sdes_transport_);
+  EXPECT_FALSE(sdes_transport_->IsSrtpActive());
+
+  JsepTransportDescription offer_desc;
+  offer_desc.cryptos.push_back(cricket::CryptoParams(
+      1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40),
+      std::string()));
+  ASSERT_TRUE(
+      jsep_transport_
+          ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer)
+          .ok());
+
+  JsepTransportDescription answer_desc;
+  answer_desc.cryptos.push_back(cricket::CryptoParams(
+      1, rtc::kCsAesCm128HmacSha1_80, "inline:" + rtc::CreateRandomString(40),
+      std::string()));
+  // Expected to fail because the crypto parameters don't match.
+  ASSERT_FALSE(
+      jsep_transport_
+          ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer)
+          .ok());
+}
+
 // Tests that the remote candidates can be added to the transports after both
 // local and remote descriptions are set.
 TEST_F(JsepTransport2Test, AddRemoteCandidates) {
-  jsep_transport_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/true);
+  jsep_transport_ =
+      CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kDtlsSrtp);
   auto fake_ice_transport = static_cast<FakeIceTransport*>(
       jsep_transport_->rtp_dtls_transport()->ice_transport());
 
@@ -943,6 +1043,7 @@
 }
 
 enum class Scenario {
+  kSdes,
   kDtlsBeforeCallerSendOffer,
   kDtlsBeforeCallerSetAnswer,
   kDtlsAfterCallerSetAnswer,
@@ -954,9 +1055,9 @@
  protected:
   JsepTransport2HeaderExtensionTest() {}
 
-  void CreateJsepTransportPair() {
-    jsep_transport1_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/true);
-    jsep_transport2_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/true);
+  void CreateJsepTransportPair(SrtpMode mode) {
+    jsep_transport1_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/true, mode);
+    jsep_transport2_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/true, mode);
 
     auto fake_dtls1 =
         static_cast<FakeDtlsTransport*>(jsep_transport1_->rtp_dtls_transport());
@@ -968,12 +1069,14 @@
     fake_dtls2->fake_ice_transport()->SignalReadPacket.connect(
         this, &JsepTransport2HeaderExtensionTest::OnReadPacket2);
 
-    auto cert1 = rtc::RTCCertificate::Create(
-        rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
-    jsep_transport1_->rtp_dtls_transport()->SetLocalCertificate(cert1);
-    auto cert2 = rtc::RTCCertificate::Create(
-        rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
-    jsep_transport2_->rtp_dtls_transport()->SetLocalCertificate(cert2);
+    if (mode == SrtpMode::kDtlsSrtp) {
+      auto cert1 = rtc::RTCCertificate::Create(
+          rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
+      jsep_transport1_->rtp_dtls_transport()->SetLocalCertificate(cert1);
+      auto cert2 = rtc::RTCCertificate::Create(
+          rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
+      jsep_transport2_->rtp_dtls_transport()->SetLocalCertificate(cert2);
+    }
   }
 
   void OnReadPacket1(rtc::PacketTransportInternal* transport,
@@ -1060,10 +1163,17 @@
 TEST_P(JsepTransport2HeaderExtensionTest, EncryptedHeaderExtensionNegotiation) {
   Scenario scenario = std::get<0>(GetParam());
   bool use_gcm = std::get<1>(GetParam());
-  CreateJsepTransportPair();
+  SrtpMode mode = SrtpMode ::kDtlsSrtp;
+  if (scenario == Scenario::kSdes) {
+    mode = SrtpMode::kSdes;
+  }
+  CreateJsepTransportPair(mode);
   recv_encrypted_headers1_.push_back(kHeaderExtensionIDs[0]);
   recv_encrypted_headers2_.push_back(kHeaderExtensionIDs[1]);
 
+  cricket::CryptoParams sdes_param(1, rtc::kCsAesCm128HmacSha1_80,
+                                   "inline:" + rtc::CreateRandomString(40),
+                                   std::string());
   if (use_gcm) {
     auto fake_dtls1 =
         static_cast<FakeDtlsTransport*>(jsep_transport1_->rtp_dtls_transport());
@@ -1080,6 +1190,9 @@
 
   JsepTransportDescription offer_desc;
   offer_desc.encrypted_header_extension_ids = recv_encrypted_headers1_;
+  if (scenario == Scenario::kSdes) {
+    offer_desc.cryptos.push_back(sdes_param);
+  }
   ASSERT_TRUE(
       jsep_transport1_
           ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer)
@@ -1091,6 +1204,9 @@
 
   JsepTransportDescription answer_desc;
   answer_desc.encrypted_header_extension_ids = recv_encrypted_headers2_;
+  if (scenario == Scenario::kSdes) {
+    answer_desc.cryptos.push_back(sdes_param);
+  }
   ASSERT_TRUE(
       jsep_transport2_
           ->SetLocalJsepTransportDescription(answer_desc, SdpType::kAnswer)
@@ -1109,7 +1225,8 @@
           ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer)
           .ok());
 
-  if (scenario == Scenario::kDtlsAfterCallerSetAnswer) {
+  if (scenario == Scenario::kDtlsAfterCallerSetAnswer ||
+      scenario == Scenario::kSdes) {
     ConnectTransport();
   }
   EXPECT_TRUE(jsep_transport1_->rtp_transport()->IsSrtpActive());
@@ -1148,6 +1265,7 @@
     JsepTransport2Test,
     JsepTransport2HeaderExtensionTest,
     ::testing::Values(
+        std::make_tuple(Scenario::kSdes, false),
         std::make_tuple(Scenario::kDtlsBeforeCallerSendOffer, true),
         std::make_tuple(Scenario::kDtlsBeforeCallerSetAnswer, true),
         std::make_tuple(Scenario::kDtlsAfterCallerSetAnswer, true),
@@ -1157,7 +1275,8 @@
 
 // This test verifies the ICE parameters are properly applied to the transports.
 TEST_F(JsepTransport2Test, SetIceParametersWithRenomination) {
-  jsep_transport_ = CreateJsepTransport2(/* rtcp_mux_enabled= */ true);
+  jsep_transport_ =
+      CreateJsepTransport2(/* rtcp_mux_enabled= */ true, SrtpMode::kDtlsSrtp);
 
   JsepTransportDescription jsep_description;
   jsep_description.transport_desc = TransportDescription(kIceUfrag1, kIcePwd1);
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 00daacd..c64924a 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -23,6 +23,7 @@
 #include "absl/strings/match.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/optional.h"
+#include "api/crypto_params.h"
 #include "api/video_codecs/h264_profile_level_id.h"
 #include "media/base/codec.h"
 #include "media/base/media_constants.h"
@@ -47,6 +48,19 @@
 using rtc::UniqueRandomIdGenerator;
 using webrtc::RtpTransceiverDirection;
 
+const char kInline[] = "inline:";
+
+void GetSupportedSdesCryptoSuiteNames(
+    void (*func)(const webrtc::CryptoOptions&, std::vector<int>*),
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<std::string>* names) {
+  std::vector<int> crypto_suites;
+  func(crypto_options, &crypto_suites);
+  for (const auto crypto : crypto_suites) {
+    names->push_back(rtc::SrtpCryptoSuiteToName(crypto));
+  }
+}
+
 webrtc::RtpExtension RtpExtensionFromCapability(
     const webrtc::RtpHeaderExtensionCapability& capability) {
   return webrtc::RtpExtension(capability.uri,
@@ -121,6 +135,157 @@
   return content->media_description()->type() == media_type;
 }
 
+static bool CreateCryptoParams(int tag,
+                               const std::string& cipher,
+                               CryptoParams* crypto_out) {
+  int key_len;
+  int salt_len;
+  if (!rtc::GetSrtpKeyAndSaltLengths(rtc::SrtpCryptoSuiteFromName(cipher),
+                                     &key_len, &salt_len)) {
+    return false;
+  }
+
+  int master_key_len = key_len + salt_len;
+  std::string master_key;
+  if (!rtc::CreateRandomData(master_key_len, &master_key)) {
+    return false;
+  }
+
+  RTC_CHECK_EQ(master_key_len, master_key.size());
+  std::string key = rtc::Base64::Encode(master_key);
+
+  crypto_out->tag = tag;
+  crypto_out->cipher_suite = cipher;
+  crypto_out->key_params = kInline;
+  crypto_out->key_params += key;
+  return true;
+}
+
+static bool AddCryptoParams(const std::string& cipher_suite,
+                            CryptoParamsVec* cryptos_out) {
+  int size = static_cast<int>(cryptos_out->size());
+
+  cryptos_out->resize(size + 1);
+  return CreateCryptoParams(size, cipher_suite, &cryptos_out->at(size));
+}
+
+void AddMediaCryptos(const CryptoParamsVec& cryptos,
+                     MediaContentDescription* media) {
+  for (const CryptoParams& crypto : cryptos) {
+    media->AddCrypto(crypto);
+  }
+}
+
+bool CreateMediaCryptos(const std::vector<std::string>& crypto_suites,
+                        MediaContentDescription* media) {
+  CryptoParamsVec cryptos;
+  for (const std::string& crypto_suite : crypto_suites) {
+    if (!AddCryptoParams(crypto_suite, &cryptos)) {
+      return false;
+    }
+  }
+  AddMediaCryptos(cryptos, media);
+  return true;
+}
+
+const CryptoParamsVec* GetCryptos(const ContentInfo* content) {
+  if (!content || !content->media_description()) {
+    return nullptr;
+  }
+  return &content->media_description()->cryptos();
+}
+
+bool FindMatchingCrypto(const CryptoParamsVec& cryptos,
+                        const CryptoParams& crypto,
+                        CryptoParams* crypto_out) {
+  auto it = absl::c_find_if(
+      cryptos, [&crypto](const CryptoParams& c) { return crypto.Matches(c); });
+  if (it == cryptos.end()) {
+    return false;
+  }
+  *crypto_out = *it;
+  return true;
+}
+
+// For audio, HMAC 32 (if enabled) is prefered over HMAC 80 because of the
+// low overhead.
+void GetSupportedAudioSdesCryptoSuites(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<int>* crypto_suites) {
+  if (crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher) {
+    crypto_suites->push_back(rtc::kSrtpAes128CmSha1_32);
+  }
+  crypto_suites->push_back(rtc::kSrtpAes128CmSha1_80);
+  if (crypto_options.srtp.enable_gcm_crypto_suites) {
+    crypto_suites->push_back(rtc::kSrtpAeadAes256Gcm);
+    crypto_suites->push_back(rtc::kSrtpAeadAes128Gcm);
+  }
+}
+
+void GetSupportedAudioSdesCryptoSuiteNames(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<std::string>* crypto_suite_names) {
+  GetSupportedSdesCryptoSuiteNames(GetSupportedAudioSdesCryptoSuites,
+                                   crypto_options, crypto_suite_names);
+}
+
+void GetSupportedVideoSdesCryptoSuites(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<int>* crypto_suites) {
+  crypto_suites->push_back(rtc::kSrtpAes128CmSha1_80);
+  if (crypto_options.srtp.enable_gcm_crypto_suites) {
+    crypto_suites->push_back(rtc::kSrtpAeadAes256Gcm);
+    crypto_suites->push_back(rtc::kSrtpAeadAes128Gcm);
+  }
+}
+
+void GetSupportedVideoSdesCryptoSuiteNames(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<std::string>* crypto_suite_names) {
+  GetSupportedSdesCryptoSuiteNames(GetSupportedVideoSdesCryptoSuites,
+                                   crypto_options, crypto_suite_names);
+}
+
+void GetSupportedDataSdesCryptoSuites(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<int>* crypto_suites) {
+  crypto_suites->push_back(rtc::kSrtpAes128CmSha1_80);
+  if (crypto_options.srtp.enable_gcm_crypto_suites) {
+    crypto_suites->push_back(rtc::kSrtpAeadAes256Gcm);
+    crypto_suites->push_back(rtc::kSrtpAeadAes128Gcm);
+  }
+}
+
+void GetSupportedDataSdesCryptoSuiteNames(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<std::string>* crypto_suite_names) {
+  GetSupportedSdesCryptoSuiteNames(GetSupportedDataSdesCryptoSuites,
+                                   crypto_options, crypto_suite_names);
+}
+
+// Support any GCM cipher (if enabled through options). For video support only
+// 80-bit SHA1 HMAC. For audio 32-bit HMAC is tolerated (if enabled) unless
+// bundle is enabled because it is low overhead.
+// Pick the crypto in the list that is supported.
+static bool SelectCrypto(const MediaContentDescription* offer,
+                         bool bundle,
+                         const webrtc::CryptoOptions& crypto_options,
+                         CryptoParams* crypto_out) {
+  bool audio = offer->type() == MEDIA_TYPE_AUDIO;
+  const CryptoParamsVec& cryptos = offer->cryptos();
+
+  for (const CryptoParams& crypto : cryptos) {
+    if ((crypto_options.srtp.enable_gcm_crypto_suites &&
+         rtc::IsGcmCryptoSuiteName(crypto.cipher_suite)) ||
+        rtc::kCsAesCm128HmacSha1_80 == crypto.cipher_suite ||
+        (rtc::kCsAesCm128HmacSha1_32 == crypto.cipher_suite && audio &&
+         !bundle && crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher)) {
+      return CreateCryptoParams(crypto.tag, crypto.cipher_suite, crypto_out);
+    }
+  }
+  return false;
+}
+
 // Finds all StreamParams of all media types and attach them to stream_params.
 static StreamParamsVec GetCurrentStreamParams(
     const std::vector<const ContentInfo*>& active_local_contents) {
@@ -318,6 +483,119 @@
   return true;
 }
 
+// Gets the CryptoParamsVec of the given `content_name` from `sdesc`, and
+// sets it to `cryptos`.
+static bool GetCryptosByName(const SessionDescription* sdesc,
+                             const std::string& content_name,
+                             CryptoParamsVec* cryptos) {
+  if (!sdesc || !cryptos) {
+    return false;
+  }
+  const ContentInfo* content = sdesc->GetContentByName(content_name);
+  if (!content || !content->media_description()) {
+    return false;
+  }
+  *cryptos = content->media_description()->cryptos();
+  return true;
+}
+
+// Prunes the `target_cryptos` by removing the crypto params (cipher_suite)
+// which are not available in `filter`.
+static void PruneCryptos(const CryptoParamsVec& filter,
+                         CryptoParamsVec* target_cryptos) {
+  if (!target_cryptos) {
+    return;
+  }
+
+  target_cryptos->erase(
+      std::remove_if(target_cryptos->begin(), target_cryptos->end(),
+                     // Returns true if the `crypto`'s cipher_suite is not
+                     // found in `filter`.
+                     [&filter](const CryptoParams& crypto) {
+                       for (const CryptoParams& entry : filter) {
+                         if (entry.cipher_suite == crypto.cipher_suite)
+                           return false;
+                       }
+                       return true;
+                     }),
+      target_cryptos->end());
+}
+
+static bool IsRtpContent(SessionDescription* sdesc,
+                         const std::string& content_name) {
+  bool is_rtp = false;
+  ContentInfo* content = sdesc->GetContentByName(content_name);
+  if (content && content->media_description()) {
+    is_rtp = IsRtpProtocol(content->media_description()->protocol());
+  }
+  return is_rtp;
+}
+
+// Updates the crypto parameters of the `sdesc` according to the given
+// `bundle_group`. The crypto parameters of all the contents within the
+// `bundle_group` should be updated to use the common subset of the
+// available cryptos.
+static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group,
+                                        SessionDescription* sdesc) {
+  // The bundle should not be empty.
+  if (!sdesc || !bundle_group.FirstContentName()) {
+    return false;
+  }
+
+  bool common_cryptos_needed = false;
+  // Get the common cryptos.
+  const ContentNames& content_names = bundle_group.content_names();
+  CryptoParamsVec common_cryptos;
+  bool first = true;
+  for (const std::string& content_name : content_names) {
+    if (!IsRtpContent(sdesc, content_name)) {
+      continue;
+    }
+    // The common cryptos are needed if any of the content does not have DTLS
+    // enabled.
+    if (!sdesc->GetTransportInfoByName(content_name)->description.secure()) {
+      common_cryptos_needed = true;
+    }
+    if (first) {
+      first = false;
+      // Initial the common_cryptos with the first content in the bundle group.
+      if (!GetCryptosByName(sdesc, content_name, &common_cryptos)) {
+        return false;
+      }
+      if (common_cryptos.empty()) {
+        // If there's no crypto params, we should just return.
+        return true;
+      }
+    } else {
+      CryptoParamsVec cryptos;
+      if (!GetCryptosByName(sdesc, content_name, &cryptos)) {
+        return false;
+      }
+      PruneCryptos(cryptos, &common_cryptos);
+    }
+  }
+
+  if (common_cryptos.empty() && common_cryptos_needed) {
+    return false;
+  }
+
+  // Update to use the common cryptos.
+  for (const std::string& content_name : content_names) {
+    if (!IsRtpContent(sdesc, content_name)) {
+      continue;
+    }
+    ContentInfo* content = sdesc->GetContentByName(content_name);
+    if (IsMediaContent(content)) {
+      MediaContentDescription* media_desc = content->media_description();
+      if (!media_desc) {
+        return false;
+      }
+      media_desc->set_cryptos(common_cryptos);
+    }
+  }
+  return true;
+}
+
 static std::vector<const ContentInfo*> GetActiveContents(
     const SessionDescription& description,
     const MediaSessionOptions& session_options) {
@@ -371,11 +649,17 @@
 }
 
 // Create a media content to be offered for the given `sender_options`,
-// according to the given parameters.
-// The created content is added to the offer.
+// according to the given options.rtcp_mux, session_options.is_muc, codecs,
+// secure_transport, crypto, and current_streams. If we don't currently have
+// crypto (in current_cryptos) and it is enabled (in secure_policy), crypto is
+// created (according to crypto_suites). The created content is added to the
+// offer.
 static bool CreateContentOffer(
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
+    const SecurePolicy& secure_policy,
+    const CryptoParamsVec* current_cryptos,
+    const std::vector<std::string>& crypto_suites,
     const RtpHeaderExtensions& rtp_extensions,
     UniqueRandomIdGenerator* ssrc_generator,
     StreamParamsVec* current_streams,
@@ -402,6 +686,20 @@
 
   AddSimulcastToMediaDescription(media_description_options, offer);
 
+  if (secure_policy != SEC_DISABLED) {
+    if (current_cryptos) {
+      AddMediaCryptos(*current_cryptos, offer);
+    }
+    if (offer->cryptos().empty()) {
+      if (!CreateMediaCryptos(crypto_suites, offer)) {
+        return false;
+      }
+    }
+  }
+
+  if (secure_policy == SEC_REQUIRED && offer->cryptos().empty()) {
+    return false;
+  }
   return true;
 }
 template <class C>
@@ -409,6 +707,9 @@
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
     const std::vector<C>& codecs,
+    const SecurePolicy& secure_policy,
+    const CryptoParamsVec* current_cryptos,
+    const std::vector<std::string>& crypto_suites,
     const RtpHeaderExtensions& rtp_extensions,
     UniqueRandomIdGenerator* ssrc_generator,
     StreamParamsVec* current_streams,
@@ -421,6 +722,7 @@
   }
 
   return CreateContentOffer(media_description_options, session_options,
+                            secure_policy, current_cryptos, crypto_suites,
                             rtp_extensions, ssrc_generator, current_streams,
                             offer);
 }
@@ -1042,13 +1344,17 @@
 
 // Create a media content to be answered for the given `sender_options`
 // according to the given session_options.rtcp_mux, session_options.streams,
-// codecs, and current_streams. The codecs and rtcp_mux are all
+// codecs, crypto, and current_streams.  If we don't currently have crypto (in
+// current_cryptos) and it is enabled (in secure_policy), crypto is created
+// (according to crypto_suites). The codecs, rtcp_mux, and crypto are all
 // negotiated with the offer. If the negotiation fails, this method returns
 // false.  The created content is added to the offer.
 static bool CreateMediaContentAnswer(
     const MediaContentDescription* offer,
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
+    const SecurePolicy& sdes_policy,
+    const CryptoParamsVec* current_cryptos,
     const RtpHeaderExtensions& local_rtp_extensions,
     UniqueRandomIdGenerator* ssrc_generator,
     bool enable_encrypted_rtp_header_extensions,
@@ -1073,6 +1379,21 @@
 
   answer->set_remote_estimate(offer->remote_estimate());
 
+  if (sdes_policy != SEC_DISABLED) {
+    CryptoParams crypto;
+    if (SelectCrypto(offer, bundle_enabled, session_options.crypto_options,
+                     &crypto)) {
+      if (current_cryptos) {
+        FindMatchingCrypto(*current_cryptos, crypto, &crypto);
+      }
+      answer->AddCrypto(crypto);
+    }
+  }
+
+  if (answer->cryptos().empty() && sdes_policy == SEC_REQUIRED) {
+    return false;
+  }
+
   AddSimulcastToMediaDescription(media_description_options, answer);
 
   answer->set_direction(NegotiateRtpTransceiverDirection(
@@ -1112,7 +1433,9 @@
 
 static void SetMediaProtocol(bool secure_transport,
                              MediaContentDescription* desc) {
-  if (secure_transport)
+  if (!desc->cryptos().empty())
+    desc->set_protocol(kMediaProtocolSavpf);
+  else if (secure_transport)
     desc->set_protocol(kMediaProtocolDtlsSavpf);
   else
     desc->set_protocol(kMediaProtocolAvpf);
@@ -1134,6 +1457,23 @@
   return desc;
 }
 
+// Gets the current DTLS state from the transport description.
+static bool IsDtlsActive(const ContentInfo* content,
+                         const SessionDescription* current_description) {
+  if (!content) {
+    return false;
+  }
+
+  size_t msection_index = content - &current_description->contents()[0];
+
+  if (current_description->transport_infos().size() <= msection_index) {
+    return false;
+  }
+
+  return current_description->transport_infos()[msection_index]
+      .description.secure();
+}
+
 void MediaDescriptionOptions::AddAudioSender(
     const std::string& track_id,
     const std::vector<std::string>& stream_ids) {
@@ -1372,6 +1712,11 @@
             << "CreateOffer failed to UpdateTransportInfoForBundle.";
         return nullptr;
       }
+      if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) {
+        RTC_LOG(LS_ERROR)
+            << "CreateOffer failed to UpdateCryptoParamsForBundle.";
+        return nullptr;
+      }
     }
   }
 
@@ -1462,21 +1807,16 @@
     RTC_DCHECK(media_description_options.mid == offer_content->name);
     // Get the index of the BUNDLE group that this MID belongs to, if any.
     absl::optional<size_t> bundle_index;
-    bool require_transport_attributes = true;
     for (size_t i = 0; i < offer_bundles.size(); ++i) {
       if (offer_bundles[i]->HasContentName(media_description_options.mid)) {
         bundle_index = i;
-        if (offer_bundles[i]->FirstContentName() &&
-            *offer_bundles[i]->FirstContentName() !=
-                media_description_options.mid) {
-          require_transport_attributes = false;
-        }
         break;
       }
     }
     TransportInfo* bundle_transport =
         bundle_index.has_value() ? bundle_transports[bundle_index.value()].get()
                                  : nullptr;
+
     const ContentInfo* current_content = nullptr;
     if (current_description &&
         msection_index < current_description->contents().size()) {
@@ -1490,9 +1830,8 @@
         if (!AddAudioContentForAnswer(
                 media_description_options, session_options, offer_content,
                 offer, current_content, current_description, bundle_transport,
-                require_transport_attributes, answer_audio_codecs,
-                header_extensions, &current_streams, answer.get(),
-                &ice_credentials)) {
+                answer_audio_codecs, header_extensions, &current_streams,
+                answer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1500,9 +1839,8 @@
         if (!AddVideoContentForAnswer(
                 media_description_options, session_options, offer_content,
                 offer, current_content, current_description, bundle_transport,
-                require_transport_attributes, answer_video_codecs,
-                header_extensions, &current_streams, answer.get(),
-                &ice_credentials)) {
+                answer_video_codecs, header_extensions, &current_streams,
+                answer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1510,8 +1848,7 @@
         if (!AddDataContentForAnswer(
                 media_description_options, session_options, offer_content,
                 offer, current_content, current_description, bundle_transport,
-                require_transport_attributes, &current_streams, answer.get(),
-                &ice_credentials)) {
+                &current_streams, answer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1519,7 +1856,7 @@
         if (!AddUnsupportedContentForAnswer(
                 media_description_options, session_options, offer_content,
                 offer, current_content, current_description, bundle_transport,
-                require_transport_attributes, answer.get(), &ice_credentials)) {
+                answer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1558,6 +1895,12 @@
               << "CreateAnswer failed to UpdateTransportInfoForBundle.";
           return NULL;
         }
+
+        if (!UpdateCryptoParamsForBundle(answer_bundle, answer.get())) {
+          RTC_LOG(LS_ERROR)
+              << "CreateAnswer failed to UpdateCryptoParamsForBundle.";
+          return NULL;
+        }
       }
     }
   }
@@ -1951,14 +2294,23 @@
     StripCNCodecs(&filtered_codecs);
   }
 
+  cricket::SecurePolicy sdes_policy =
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
+
   auto audio = std::make_unique<AudioContentDescription>();
+  std::vector<std::string> crypto_suites;
+  GetSupportedAudioSdesCryptoSuiteNames(session_options.crypto_options,
+                                        &crypto_suites);
   if (!CreateMediaContentOffer(media_description_options, session_options,
-                               filtered_codecs, audio_rtp_extensions,
-                               ssrc_generator_, current_streams, audio.get())) {
+                               filtered_codecs, sdes_policy,
+                               GetCryptos(current_content), crypto_suites,
+                               audio_rtp_extensions, ssrc_generator_,
+                               current_streams, audio.get())) {
     return false;
   }
 
-  bool secure_transport = transport_desc_factory_->IsEncrypted();
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, audio.get());
 
   audio->set_direction(media_description_options.direction);
@@ -2036,16 +2388,24 @@
     }
   }
 
+  cricket::SecurePolicy sdes_policy =
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
   auto video = std::make_unique<VideoContentDescription>();
+  std::vector<std::string> crypto_suites;
+  GetSupportedVideoSdesCryptoSuiteNames(session_options.crypto_options,
+                                        &crypto_suites);
   if (!CreateMediaContentOffer(media_description_options, session_options,
-                               filtered_codecs, video_rtp_extensions,
-                               ssrc_generator_, current_streams, video.get())) {
+                               filtered_codecs, sdes_policy,
+                               GetCryptos(current_content), crypto_suites,
+                               video_rtp_extensions, ssrc_generator_,
+                               current_streams, video.get())) {
     return false;
   }
 
   video->set_bandwidth(kAutoBandwidth);
 
-  bool secure_transport = transport_desc_factory_->IsEncrypted();
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, video.get());
 
   video->set_direction(media_description_options.direction);
@@ -2071,8 +2431,15 @@
     IceCredentialsIterator* ice_credentials) const {
   auto data = std::make_unique<SctpDataContentDescription>();
 
-  bool secure_transport = transport_desc_factory_->IsEncrypted();
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
 
+  cricket::SecurePolicy sdes_policy =
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
+  std::vector<std::string> crypto_suites;
+  // SDES doesn't make sense for SCTP, so we disable it, and we only
+  // get SDES crypto suites for RTP-based data channels.
+  sdes_policy = cricket::SEC_DISABLED;
   // Unlike SetMediaProtocol below, we need to set the protocol
   // before we call CreateMediaContentOffer.  Otherwise,
   // CreateMediaContentOffer won't know this is SCTP and will
@@ -2083,7 +2450,8 @@
   data->set_max_message_size(kSctpSendBufferSize);
 
   if (!CreateContentOffer(media_description_options, session_options,
-                          RtpHeaderExtensions(), ssrc_generator_,
+                          sdes_policy, GetCryptos(current_content),
+                          crypto_suites, RtpHeaderExtensions(), ssrc_generator_,
                           current_streams, data.get())) {
     return false;
   }
@@ -2143,7 +2511,6 @@
     const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
-    bool require_transport_attributes,
     const AudioCodecs& audio_codecs,
     const RtpHeaderExtensions& default_audio_rtp_header_extensions,
     StreamParamsVec* current_streams,
@@ -2156,7 +2523,7 @@
   std::unique_ptr<TransportDescription> audio_transport = CreateTransportAnswer(
       media_description_options.mid, offer_description,
       media_description_options.transport_options, current_description,
-      require_transport_attributes, ice_credentials);
+      bundle_transport != nullptr, ice_credentials);
   if (!audio_transport) {
     return false;
   }
@@ -2211,6 +2578,9 @@
   bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
                         session_options.bundle_enabled;
   auto audio_answer = std::make_unique<AudioContentDescription>();
+  // Do not require or create SDES cryptos if DTLS is used.
+  cricket::SecurePolicy sdes_policy =
+      audio_transport->secure() ? cricket::SEC_DISABLED : secure();
   if (!SetCodecsInAnswer(offer_audio_description, filtered_codecs,
                          media_description_options, session_options,
                          ssrc_generator_, current_streams,
@@ -2219,6 +2589,7 @@
   }
   if (!CreateMediaContentAnswer(
           offer_audio_description, media_description_options, session_options,
+          sdes_policy, GetCryptos(current_content),
           filtered_rtp_header_extensions(default_audio_rtp_header_extensions),
           ssrc_generator_, enable_encrypted_rtp_header_extensions_,
           current_streams, bundle_enabled, audio_answer.get())) {
@@ -2256,7 +2627,6 @@
     const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
-    bool require_transport_attributes,
     const VideoCodecs& video_codecs,
     const RtpHeaderExtensions& default_video_rtp_header_extensions,
     StreamParamsVec* current_streams,
@@ -2269,7 +2639,7 @@
   std::unique_ptr<TransportDescription> video_transport = CreateTransportAnswer(
       media_description_options.mid, offer_description,
       media_description_options.transport_options, current_description,
-      require_transport_attributes, ice_credentials);
+      bundle_transport != nullptr, ice_credentials);
   if (!video_transport) {
     return false;
   }
@@ -2328,6 +2698,9 @@
   bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
                         session_options.bundle_enabled;
   auto video_answer = std::make_unique<VideoContentDescription>();
+  // Do not require or create SDES cryptos if DTLS is used.
+  cricket::SecurePolicy sdes_policy =
+      video_transport->secure() ? cricket::SEC_DISABLED : secure();
   if (!SetCodecsInAnswer(offer_video_description, filtered_codecs,
                          media_description_options, session_options,
                          ssrc_generator_, current_streams,
@@ -2336,6 +2709,7 @@
   }
   if (!CreateMediaContentAnswer(
           offer_video_description, media_description_options, session_options,
+          sdes_policy, GetCryptos(current_content),
           filtered_rtp_header_extensions(default_video_rtp_header_extensions),
           ssrc_generator_, enable_encrypted_rtp_header_extensions_,
           current_streams, bundle_enabled, video_answer.get())) {
@@ -2371,18 +2745,20 @@
     const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
-    bool require_transport_attributes,
     StreamParamsVec* current_streams,
     SessionDescription* answer,
     IceCredentialsIterator* ice_credentials) const {
   std::unique_ptr<TransportDescription> data_transport = CreateTransportAnswer(
       media_description_options.mid, offer_description,
       media_description_options.transport_options, current_description,
-      require_transport_attributes, ice_credentials);
+      bundle_transport != nullptr, ice_credentials);
   if (!data_transport) {
     return false;
   }
 
+  // Do not require or create SDES cryptos if DTLS is used.
+  cricket::SecurePolicy sdes_policy =
+      data_transport->secure() ? cricket::SEC_DISABLED : secure();
   bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
                         session_options.bundle_enabled;
   RTC_CHECK(IsMediaContentOfType(offer_content, MEDIA_TYPE_DATA));
@@ -2407,9 +2783,9 @@
     }
     if (!CreateMediaContentAnswer(
             offer_data_description, media_description_options, session_options,
-            RtpHeaderExtensions(), ssrc_generator_,
-            enable_encrypted_rtp_header_extensions_, current_streams,
-            bundle_enabled, data_answer.get())) {
+            sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(),
+            ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+            current_streams, bundle_enabled, data_answer.get())) {
       return false;  // Fails the session setup.
     }
     // Respond with sctpmap if the offer uses sctpmap.
@@ -2444,13 +2820,12 @@
     const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
-    bool require_transport_attributes,
     SessionDescription* answer,
     IceCredentialsIterator* ice_credentials) const {
   std::unique_ptr<TransportDescription> unsupported_transport =
       CreateTransportAnswer(media_description_options.mid, offer_description,
                             media_description_options.transport_options,
-                            current_description, require_transport_attributes,
+                            current_description, bundle_transport != nullptr,
                             ice_credentials);
   if (!unsupported_transport) {
     return false;
diff --git a/pc/media_session.h b/pc/media_session.h
index b648ba6..bb97f42 100644
--- a/pc/media_session.h
+++ b/pc/media_session.h
@@ -156,6 +156,8 @@
                         const VideoCodecs& recv_codecs);
   RtpHeaderExtensions filtered_rtp_header_extensions(
       RtpHeaderExtensions extensions) const;
+  SecurePolicy secure() const { return secure_; }
+  void set_secure(SecurePolicy s) { secure_ = s; }
 
   void set_enable_encrypted_rtp_header_extensions(bool enable) {
     enable_encrypted_rtp_header_extensions_ = enable;
@@ -272,7 +274,6 @@
       const ContentInfo* current_content,
       const SessionDescription* current_description,
       const TransportInfo* bundle_transport,
-      bool require_transport_attributes,
       const AudioCodecs& audio_codecs,
       const RtpHeaderExtensions& default_audio_rtp_header_extensions,
       StreamParamsVec* current_streams,
@@ -287,7 +288,6 @@
       const ContentInfo* current_content,
       const SessionDescription* current_description,
       const TransportInfo* bundle_transport,
-      bool require_transport_attributes,
       const VideoCodecs& video_codecs,
       const RtpHeaderExtensions& default_video_rtp_header_extensions,
       StreamParamsVec* current_streams,
@@ -302,7 +302,6 @@
       const ContentInfo* current_content,
       const SessionDescription* current_description,
       const TransportInfo* bundle_transport,
-      bool require_transport_attributes,
       StreamParamsVec* current_streams,
       SessionDescription* answer,
       IceCredentialsIterator* ice_credentials) const;
@@ -315,7 +314,6 @@
       const ContentInfo* current_content,
       const SessionDescription* current_description,
       const TransportInfo* bundle_transport,
-      bool require_transport_attributes,
       SessionDescription* answer,
       IceCredentialsIterator* ice_credentials) const;
 
@@ -339,6 +337,9 @@
   // This object is not owned by the channel so it must outlive it.
   rtc::UniqueRandomIdGenerator* const ssrc_generator_;
   bool enable_encrypted_rtp_header_extensions_ = false;
+  // TODO(zhihuang): Rename secure_ to sdec_policy_; rename the related getter
+  // and setter.
+  SecurePolicy secure_ = SEC_DISABLED;
   const TransportDescriptionFactory* transport_desc_factory_;
 };
 
@@ -382,6 +383,26 @@
 SctpDataContentDescription* GetFirstSctpDataContentDescription(
     SessionDescription* sdesc);
 
+// Helper functions to return crypto suites used for SDES.
+void GetSupportedAudioSdesCryptoSuites(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<int>* crypto_suites);
+void GetSupportedVideoSdesCryptoSuites(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<int>* crypto_suites);
+void GetSupportedDataSdesCryptoSuites(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<int>* crypto_suites);
+void GetSupportedAudioSdesCryptoSuiteNames(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<std::string>* crypto_suite_names);
+void GetSupportedVideoSdesCryptoSuiteNames(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<std::string>* crypto_suite_names);
+void GetSupportedDataSdesCryptoSuiteNames(
+    const webrtc::CryptoOptions& crypto_options,
+    std::vector<std::string>* crypto_suite_names);
+
 }  // namespace cricket
 
 #endif  // PC_MEDIA_SESSION_H_
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc
index 9adc231..a02b4c1 100644
--- a/pc/media_session_unittest.cc
+++ b/pc/media_session_unittest.cc
@@ -26,6 +26,7 @@
 #include "p2p/base/transport_description.h"
 #include "p2p/base/transport_info.h"
 #include "pc/rtp_media_utils.h"
+#include "pc/srtp_filter.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/fake_ssl_identity.h"
 #include "rtc_base/gunit.h"
@@ -36,11 +37,16 @@
 #include "test/field_trial.h"
 #include "test/gmock.h"
 
+#define ASSERT_CRYPTO(cd, s, cs)      \
+  ASSERT_EQ(s, cd->cryptos().size()); \
+  ASSERT_EQ(cs, cd->cryptos()[0].cipher_suite)
+
 typedef std::vector<cricket::Candidate> Candidates;
 
 using cricket::AudioCodec;
 using cricket::AudioContentDescription;
 using cricket::ContentInfo;
+using cricket::CryptoParamsVec;
 using cricket::GetFirstAudioContent;
 using cricket::GetFirstAudioContentDescription;
 using cricket::GetFirstDataContent;
@@ -59,6 +65,9 @@
 using cricket::RidDescription;
 using cricket::RidDirection;
 using cricket::SctpDataContentDescription;
+using cricket::SEC_DISABLED;
+using cricket::SEC_ENABLED;
+using cricket::SEC_REQUIRED;
 using cricket::SessionDescription;
 using cricket::SimulcastDescription;
 using cricket::SimulcastLayer;
@@ -256,6 +265,11 @@
     "TCP/TLS/RTP/SAVPF", "TCP/TLS/RTP/SAVP", "UDP/TLS/RTP/SAVPF",
     "UDP/TLS/RTP/SAVP"};
 
+// SRTP cipher name negotiated by the tests. This must be updated if the
+// default changes.
+static const char* kDefaultSrtpCryptoSuite = kCsAesCm128HmacSha1_80;
+static const char* kDefaultSrtpCryptoSuiteGcm = kCsAeadAes256Gcm;
+
 // These constants are used to make the code using "AddMediaDescriptionOptions"
 // more readable.
 static constexpr bool kStopped = true;
@@ -389,6 +403,17 @@
   return session_options;
 }
 
+// prefers GCM SDES crypto suites by removing non-GCM defaults.
+void PreferGcmCryptoParameters(CryptoParamsVec* cryptos) {
+  cryptos->erase(
+      std::remove_if(cryptos->begin(), cryptos->end(),
+                     [](const cricket::CryptoParams& crypto) {
+                       return crypto.cipher_suite != kCsAeadAes256Gcm &&
+                              crypto.cipher_suite != kCsAeadAes128Gcm;
+                     }),
+      cryptos->end());
+}
+
 // TODO(zhihuang): Most of these tests were written while MediaSessionOptions
 // was designed for Plan B SDP, where only one audio "m=" section and one video
 // "m=" section could be generated, and ordering couldn't be controlled. Many of
@@ -405,7 +430,10 @@
                          MAKE_VECTOR(kAudioCodecs2));
     f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2),
                          MAKE_VECTOR(kVideoCodecs2));
-    SetDtls(true);
+    tdf1_.set_certificate(rtc::RTCCertificate::Create(
+        std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id1"))));
+    tdf2_.set_certificate(rtc::RTCCertificate::Create(
+        std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id2"))));
   }
 
   // Create a video StreamParamsVec object with:
@@ -435,6 +463,18 @@
     return video_streams;
   }
 
+  bool CompareCryptoParams(const CryptoParamsVec& c1,
+                           const CryptoParamsVec& c2) {
+    if (c1.size() != c2.size())
+      return false;
+    for (size_t i = 0; i < c1.size(); ++i)
+      if (c1[i].tag != c2[i].tag || c1[i].cipher_suite != c2[i].cipher_suite ||
+          c1[i].key_params != c2[i].key_params ||
+          c1[i].session_params != c2[i].session_params)
+        return false;
+    return true;
+  }
+
   // Returns true if the transport info contains "renomination" as an
   // ICE option.
   bool GetIceRenomination(const TransportInfo* transport_info) {
@@ -547,6 +587,50 @@
     }
   }
 
+  void TestCryptoWithBundle(bool offer) {
+    f1_.set_secure(SEC_ENABLED);
+    MediaSessionOptions options;
+    AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
+    std::unique_ptr<SessionDescription> ref_desc;
+    std::unique_ptr<SessionDescription> desc;
+    if (offer) {
+      options.bundle_enabled = false;
+      ref_desc = f1_.CreateOffer(options, NULL);
+      options.bundle_enabled = true;
+      desc = f1_.CreateOffer(options, ref_desc.get());
+    } else {
+      options.bundle_enabled = true;
+      ref_desc = f1_.CreateOffer(options, NULL);
+      desc = f1_.CreateAnswer(ref_desc.get(), options, NULL);
+    }
+    ASSERT_TRUE(desc);
+    const cricket::MediaContentDescription* audio_media_desc =
+        desc->GetContentDescriptionByName("audio");
+    ASSERT_TRUE(audio_media_desc);
+    const cricket::MediaContentDescription* video_media_desc =
+        desc->GetContentDescriptionByName("video");
+    ASSERT_TRUE(video_media_desc);
+    EXPECT_TRUE(CompareCryptoParams(audio_media_desc->cryptos(),
+                                    video_media_desc->cryptos()));
+    EXPECT_EQ(1u, audio_media_desc->cryptos().size());
+    EXPECT_EQ(kDefaultSrtpCryptoSuite,
+              audio_media_desc->cryptos()[0].cipher_suite);
+
+    // Verify the selected crypto is one from the reference audio
+    // media content.
+    const cricket::MediaContentDescription* ref_audio_media_desc =
+        ref_desc->GetContentDescriptionByName("audio");
+    bool found = false;
+    for (size_t i = 0; i < ref_audio_media_desc->cryptos().size(); ++i) {
+      if (ref_audio_media_desc->cryptos()[i].Matches(
+              audio_media_desc->cryptos()[0])) {
+        found = true;
+        break;
+      }
+    }
+    EXPECT_TRUE(found);
+  }
+
   // This test that the audio and video media direction is set to
   // `expected_direction_in_answer` in an answer if the offer direction is set
   // to `direction_in_offer` and the answer is willing to both send and receive.
@@ -590,6 +674,59 @@
     return true;
   }
 
+  void TestVideoGcmCipher(bool gcm_offer, bool gcm_answer) {
+    MediaSessionOptions offer_opts;
+    AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &offer_opts);
+    offer_opts.crypto_options.srtp.enable_gcm_crypto_suites = gcm_offer;
+
+    MediaSessionOptions answer_opts;
+    AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &answer_opts);
+    answer_opts.crypto_options.srtp.enable_gcm_crypto_suites = gcm_answer;
+
+    f1_.set_secure(SEC_ENABLED);
+    f2_.set_secure(SEC_ENABLED);
+    std::unique_ptr<SessionDescription> offer =
+        f1_.CreateOffer(offer_opts, NULL);
+    ASSERT_TRUE(offer.get() != NULL);
+    if (gcm_offer && gcm_answer) {
+      for (cricket::ContentInfo& content : offer->contents()) {
+        auto cryptos = content.media_description()->cryptos();
+        PreferGcmCryptoParameters(&cryptos);
+        content.media_description()->set_cryptos(cryptos);
+      }
+    }
+    std::unique_ptr<SessionDescription> answer =
+        f2_.CreateAnswer(offer.get(), answer_opts, NULL);
+    const ContentInfo* ac = answer->GetContentByName("audio");
+    const ContentInfo* vc = answer->GetContentByName("video");
+    ASSERT_TRUE(ac != NULL);
+    ASSERT_TRUE(vc != NULL);
+    EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
+    EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
+    const AudioContentDescription* acd = ac->media_description()->as_audio();
+    const VideoContentDescription* vcd = vc->media_description()->as_video();
+    EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+    EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
+    EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
+    EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+    EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
+    if (gcm_offer && gcm_answer) {
+      ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuiteGcm);
+    } else {
+      ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
+    }
+    EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+    EXPECT_THAT(vcd->codecs(), ElementsAreArray(kVideoCodecsAnswer));
+    EXPECT_EQ(0U, vcd->first_ssrc());  // no sender is attached
+    EXPECT_TRUE(vcd->rtcp_mux());      // negotiated rtcp-mux
+    if (gcm_offer && gcm_answer) {
+      ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuiteGcm);
+    } else {
+      ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuite);
+    }
+    EXPECT_EQ(cricket::kMediaProtocolSavpf, vcd->protocol());
+  }
+
   void TestTransportSequenceNumberNegotiation(
       const cricket::RtpHeaderExtensions& local,
       const cricket::RtpHeaderExtensions& offered,
@@ -644,24 +781,6 @@
   }
 
  protected:
-  // Helper to turn connection encryption with DTLS on or off.
-  // Default state is on.
-  void SetDtls(bool dtls_f1, bool dtls_f2) {
-    if (dtls_f1) {
-      tdf1_.set_certificate(rtc::RTCCertificate::Create(
-          std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id1"))));
-    } else {
-      tdf1_.set_certificate(nullptr);
-    }
-    if (dtls_f2) {
-      tdf2_.set_certificate(rtc::RTCCertificate::Create(
-          std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id2"))));
-    } else {
-      tdf2_.set_certificate(nullptr);
-    }
-  }
-  void SetDtls(bool dtls) { SetDtls(dtls, dtls); }
-
   UniqueRandomIdGenerator ssrc_generator1;
   UniqueRandomIdGenerator ssrc_generator2;
   MediaSessionDescriptionFactory f1_;
@@ -672,6 +791,7 @@
 
 // Create a typical audio offer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) {
+  f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer =
       f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL);
   ASSERT_TRUE(offer.get() != NULL);
@@ -686,13 +806,15 @@
   EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached.
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
-  EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, acd->protocol());
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, acd->protocol());
 }
 
 // Create a typical video offer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
+  f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* ac = offer->GetContentByName("audio");
@@ -708,13 +830,15 @@
   EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
-  EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, acd->protocol());
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, acd->protocol());
   EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
   EXPECT_EQ(f1_.video_sendrecv_codecs(), vcd->codecs());
   EXPECT_EQ(0U, vcd->first_ssrc());             // no sender is attached
   EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
-  EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, vcd->protocol());
+  ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, vcd->protocol());
 }
 
 // Test creating an offer with bundle where the Codecs have the same dynamic
@@ -744,6 +868,8 @@
 // after an audio only session has been negotiated.
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateUpdatedVideoOfferWithBundle) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
   MediaSessionOptions opts;
   AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
                              RtpTransceiverDirection::kRecvOnly, kActive,
@@ -769,16 +895,18 @@
   EXPECT_TRUE(NULL != vcd);
   EXPECT_TRUE(NULL != acd);
 
-  EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, acd->protocol());
-  EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, vcd->protocol());
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, acd->protocol());
+  ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, vcd->protocol());
 }
 
 // Create an SCTP data offer with bundle without error.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) {
-  SetDtls(false);
   MediaSessionOptions opts;
   opts.bundle_enabled = true;
   AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
+  f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   EXPECT_TRUE(offer.get() != NULL);
   EXPECT_TRUE(offer->GetContentByName("data") != NULL);
@@ -793,6 +921,8 @@
   MediaSessionOptions opts;
   opts.bundle_enabled = true;
   AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
+  f1_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   EXPECT_TRUE(offer.get() != NULL);
   EXPECT_TRUE(offer->GetContentByName("data") != NULL);
@@ -804,10 +934,10 @@
 
 // Test creating an sctp data channel from an already generated offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateImplicitSctpDataOffer) {
-  SetDtls(false);
   MediaSessionOptions opts;
   opts.bundle_enabled = true;
   AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
+  f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer1.get() != NULL);
   const ContentInfo* data = offer1->GetContentByName("data");
@@ -1108,6 +1238,8 @@
 
 // Create a typical audio answer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer =
       f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL);
   ASSERT_TRUE(offer.get() != NULL);
@@ -1124,13 +1256,47 @@
   EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
   EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
-  EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, acd->protocol());
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, acd->protocol());
+}
+
+// Create a typical audio answer with GCM ciphers enabled, and ensure it
+// matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
+  opts.crypto_options.srtp.enable_gcm_crypto_suites = true;
+  std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
+  ASSERT_TRUE(offer.get() != NULL);
+  for (cricket::ContentInfo& content : offer->contents()) {
+    auto cryptos = content.media_description()->cryptos();
+    PreferGcmCryptoParameters(&cryptos);
+    content.media_description()->set_cryptos(cryptos);
+  }
+  std::unique_ptr<SessionDescription> answer =
+      f2_.CreateAnswer(offer.get(), opts, NULL);
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc == NULL);
+  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
+  const AudioContentDescription* acd = ac->media_description()->as_audio();
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
+  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuiteGcm);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, acd->protocol());
 }
 
 // Create a typical video answer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<SessionDescription> answer =
@@ -1148,11 +1314,31 @@
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
   EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
   EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
   EXPECT_THAT(vcd->codecs(), ElementsAreArray(kVideoCodecsAnswer));
   EXPECT_EQ(0U, vcd->first_ssrc());  // no sender is attached
   EXPECT_TRUE(vcd->rtcp_mux());      // negotiated rtcp-mux
-  EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, vcd->protocol());
+  ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, vcd->protocol());
+}
+
+// Create a typical video answer with GCM ciphers enabled, and ensure it
+// matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerGcm) {
+  TestVideoGcmCipher(true, true);
+}
+
+// Create a typical video answer with GCM ciphers enabled for the offer only,
+// and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerGcmOffer) {
+  TestVideoGcmCipher(true, false);
+}
+
+// Create a typical video answer with GCM ciphers enabled for the answer only,
+// and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerGcmAnswer) {
+  TestVideoGcmCipher(false, true);
 }
 
 // The use_sctpmap flag should be set in an Sctp DataContentDescription by
@@ -1202,6 +1388,13 @@
 // and "TCP/DTLS/SCTP" offers.
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateDataAnswerToDifferentOfferedProtos) {
+  // Need to enable DTLS offer/answer generation (disabled by default in this
+  // test).
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_ENABLED);
+
   MediaSessionOptions opts;
   AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
@@ -1229,6 +1422,13 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateDataAnswerToOfferWithDefinedMessageSize) {
+  // Need to enable DTLS offer/answer generation (disabled by default in this
+  // test).
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_ENABLED);
+
   MediaSessionOptions opts;
   AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
@@ -1251,6 +1451,13 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateDataAnswerToOfferWithZeroMessageSize) {
+  // Need to enable DTLS offer/answer generation (disabled by default in this
+  // test).
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_ENABLED);
+
   MediaSessionOptions opts;
   AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
@@ -1337,10 +1544,13 @@
                              RtpTransceiverDirection::kInactive);
 }
 
-// Test that the media protocol is RTP/AVPF if DTLS is disabled.
+// Test that the media protocol is RTP/AVPF if DTLS and SDES are disabled.
 TEST_F(MediaSessionDescriptionFactoryTest, AudioOfferAnswerWithCryptoDisabled) {
   MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
-  SetDtls(false);
+  f1_.set_secure(SEC_DISABLED);
+  f2_.set_secure(SEC_DISABLED);
+  tdf1_.set_secure(SEC_DISABLED);
+  tdf2_.set_secure(SEC_DISABLED);
 
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   const AudioContentDescription* offer_acd =
@@ -2102,6 +2312,7 @@
   AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack2,
                                         {kMediaStream1}, 1, &opts);
 
+  f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
 
   ASSERT_TRUE(offer.get() != NULL);
@@ -2126,9 +2337,11 @@
 
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
 
   EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
   EXPECT_EQ(f1_.video_sendrecv_codecs(), vcd->codecs());
+  ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuite);
 
   const StreamParamsVec& video_streams = vcd->streams();
   ASSERT_EQ(1U, video_streams.size());
@@ -2161,6 +2374,10 @@
   EXPECT_EQ(acd->codecs(), updated_acd->codecs());
   EXPECT_EQ(vcd->type(), updated_vcd->type());
   EXPECT_EQ(vcd->codecs(), updated_vcd->codecs());
+  ASSERT_CRYPTO(updated_acd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+  ASSERT_CRYPTO(updated_vcd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
 
   const StreamParamsVec& updated_audio_streams = updated_acd->streams();
   ASSERT_EQ(2U, updated_audio_streams.size());
@@ -2378,6 +2595,8 @@
   AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
                              RtpTransceiverDirection::kRecvOnly, kActive,
                              &offer_opts);
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(offer_opts, NULL);
 
   MediaSessionOptions answer_opts;
@@ -2404,6 +2623,8 @@
   ASSERT_TRUE(vc != NULL);
   const AudioContentDescription* acd = ac->media_description()->as_audio();
   const VideoContentDescription* vcd = vc->media_description()->as_video();
+  ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
+  ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuite);
 
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
@@ -2449,6 +2670,11 @@
   const VideoContentDescription* updated_vcd =
       vc->media_description()->as_video();
 
+  ASSERT_CRYPTO(updated_acd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+  ASSERT_CRYPTO(updated_vcd, 1U, kDefaultSrtpCryptoSuite);
+  EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
+
   EXPECT_EQ(acd->type(), updated_acd->type());
   EXPECT_EQ(acd->codecs(), updated_acd->codecs());
   EXPECT_EQ(vcd->type(), updated_vcd->type());
@@ -3380,11 +3606,27 @@
   TestTransportInfo(false, options, true);
 }
 
+// Create an offer with bundle enabled and verify the crypto parameters are
+// the common set of the available cryptos.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoWithOfferBundle) {
+  TestCryptoWithBundle(true);
+}
+
+// Create an answer with bundle enabled and verify the crypto parameters are
+// the common set of the available cryptos.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoWithAnswerBundle) {
+  TestCryptoWithBundle(false);
+}
+
 // Verifies that creating answer fails if the offer has UDP/TLS/RTP/SAVPF but
 // DTLS is not enabled locally.
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestOfferDtlsSavpfWithoutDtlsFailed) {
-  SetDtls(true, false);
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_DISABLED);
+  tdf2_.set_secure(SEC_DISABLED);
+
   std::unique_ptr<SessionDescription> offer =
       f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL);
   ASSERT_TRUE(offer.get() != NULL);
@@ -3406,6 +3648,11 @@
 // Offers UDP/TLS/RTP/SAVPF and verifies the answer can be created and contains
 // UDP/TLS/RTP/SAVPF.
 TEST_F(MediaSessionDescriptionFactoryTest, TestOfferDtlsSavpfCreateAnswer) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_ENABLED);
+
   std::unique_ptr<SessionDescription> offer =
       f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL);
   ASSERT_TRUE(offer.get() != NULL);
@@ -3428,9 +3675,120 @@
   EXPECT_EQ(cricket::kMediaProtocolDtlsSavpf, answer_audio_desc->protocol());
 }
 
+// Test that we include both SDES and DTLS in the offer, but only include SDES
+// in the answer if DTLS isn't negotiated.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoDtls) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_DISABLED);
+  MediaSessionOptions options;
+  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
+  std::unique_ptr<SessionDescription> offer, answer;
+  const cricket::MediaContentDescription* audio_media_desc;
+  const cricket::MediaContentDescription* video_media_desc;
+  const cricket::TransportDescription* audio_trans_desc;
+  const cricket::TransportDescription* video_trans_desc;
+
+  // Generate an offer with SDES and DTLS support.
+  offer = f1_.CreateOffer(options, NULL);
+  ASSERT_TRUE(offer.get() != NULL);
+
+  audio_media_desc = offer->GetContentDescriptionByName("audio");
+  ASSERT_TRUE(audio_media_desc != NULL);
+  video_media_desc = offer->GetContentDescriptionByName("video");
+  ASSERT_TRUE(video_media_desc != NULL);
+  EXPECT_EQ(1u, audio_media_desc->cryptos().size());
+  EXPECT_EQ(1u, video_media_desc->cryptos().size());
+
+  audio_trans_desc = offer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_trans_desc != NULL);
+  video_trans_desc = offer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_trans_desc != NULL);
+  ASSERT_TRUE(audio_trans_desc->identity_fingerprint.get() != NULL);
+  ASSERT_TRUE(video_trans_desc->identity_fingerprint.get() != NULL);
+
+  // Generate an answer with only SDES support, since tdf2 has crypto disabled.
+  answer = f2_.CreateAnswer(offer.get(), options, NULL);
+  ASSERT_TRUE(answer.get() != NULL);
+
+  audio_media_desc = answer->GetContentDescriptionByName("audio");
+  ASSERT_TRUE(audio_media_desc != NULL);
+  video_media_desc = answer->GetContentDescriptionByName("video");
+  ASSERT_TRUE(video_media_desc != NULL);
+  EXPECT_EQ(1u, audio_media_desc->cryptos().size());
+  EXPECT_EQ(1u, video_media_desc->cryptos().size());
+
+  audio_trans_desc = answer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_trans_desc != NULL);
+  video_trans_desc = answer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_trans_desc != NULL);
+  ASSERT_TRUE(audio_trans_desc->identity_fingerprint.get() == NULL);
+  ASSERT_TRUE(video_trans_desc->identity_fingerprint.get() == NULL);
+
+  // Enable DTLS; the answer should now only have DTLS support.
+  tdf2_.set_secure(SEC_ENABLED);
+  answer = f2_.CreateAnswer(offer.get(), options, NULL);
+  ASSERT_TRUE(answer.get() != NULL);
+
+  audio_media_desc = answer->GetContentDescriptionByName("audio");
+  ASSERT_TRUE(audio_media_desc != NULL);
+  video_media_desc = answer->GetContentDescriptionByName("video");
+  ASSERT_TRUE(video_media_desc != NULL);
+  EXPECT_TRUE(audio_media_desc->cryptos().empty());
+  EXPECT_TRUE(video_media_desc->cryptos().empty());
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, audio_media_desc->protocol());
+  EXPECT_EQ(cricket::kMediaProtocolSavpf, video_media_desc->protocol());
+
+  audio_trans_desc = answer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_trans_desc != NULL);
+  video_trans_desc = answer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_trans_desc != NULL);
+  ASSERT_TRUE(audio_trans_desc->identity_fingerprint.get() != NULL);
+  ASSERT_TRUE(video_trans_desc->identity_fingerprint.get() != NULL);
+
+  // Try creating offer again. DTLS enabled now, crypto's should be empty
+  // in new offer.
+  offer = f1_.CreateOffer(options, offer.get());
+  ASSERT_TRUE(offer.get() != NULL);
+  audio_media_desc = offer->GetContentDescriptionByName("audio");
+  ASSERT_TRUE(audio_media_desc != NULL);
+  video_media_desc = offer->GetContentDescriptionByName("video");
+  ASSERT_TRUE(video_media_desc != NULL);
+  EXPECT_TRUE(audio_media_desc->cryptos().empty());
+  EXPECT_TRUE(video_media_desc->cryptos().empty());
+
+  audio_trans_desc = offer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_trans_desc != NULL);
+  video_trans_desc = offer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_trans_desc != NULL);
+  ASSERT_TRUE(audio_trans_desc->identity_fingerprint.get() != NULL);
+  ASSERT_TRUE(video_trans_desc->identity_fingerprint.get() != NULL);
+}
+
+// Test that an answer can't be created if cryptos are required but the offer is
+// unsecure.
+TEST_F(MediaSessionDescriptionFactoryTest, TestSecureAnswerToUnsecureOffer) {
+  MediaSessionOptions options = CreatePlanBMediaSessionOptions();
+  f1_.set_secure(SEC_DISABLED);
+  tdf1_.set_secure(SEC_DISABLED);
+  f2_.set_secure(SEC_REQUIRED);
+  tdf1_.set_secure(SEC_ENABLED);
+
+  std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(options, NULL);
+  ASSERT_TRUE(offer.get() != NULL);
+  std::unique_ptr<SessionDescription> answer =
+      f2_.CreateAnswer(offer.get(), options, NULL);
+  EXPECT_TRUE(answer.get() == NULL);
+}
+
 // Test that we accept a DTLS offer without SDES and create an appropriate
 // answer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoOfferDtlsButNotSdes) {
+  f1_.set_secure(SEC_DISABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_ENABLED);
   MediaSessionOptions options;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
 
@@ -3438,6 +3796,13 @@
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(options, NULL);
   ASSERT_TRUE(offer.get() != NULL);
 
+  const AudioContentDescription* audio_offer =
+      GetFirstAudioContentDescription(offer.get());
+  ASSERT_TRUE(audio_offer->cryptos().empty());
+  const VideoContentDescription* video_offer =
+      GetFirstVideoContentDescription(offer.get());
+  ASSERT_TRUE(video_offer->cryptos().empty());
+
   const cricket::TransportDescription* audio_offer_trans_desc =
       offer->GetTransportDescriptionByName("audio");
   ASSERT_TRUE(audio_offer_trans_desc->identity_fingerprint.get() != NULL);
@@ -3964,10 +4329,14 @@
                          MAKE_VECTOR(kAudioCodecs2));
     f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2),
                          MAKE_VECTOR(kVideoCodecs2));
+    f1_.set_secure(SEC_ENABLED);
+    f2_.set_secure(SEC_ENABLED);
     tdf1_.set_certificate(rtc::RTCCertificate::Create(
         std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id1"))));
     tdf2_.set_certificate(rtc::RTCCertificate::Create(
         std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id2"))));
+    tdf1_.set_secure(SEC_ENABLED);
+    tdf2_.set_secure(SEC_ENABLED);
   }
 
  protected:
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index de13d4c..c02518c 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -2634,7 +2634,9 @@
 
 bool PeerConnection::SrtpRequired() const {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  return dtls_enabled_;
+  return (dtls_enabled_ ||
+          sdp_handler_->webrtc_session_desc_factory()->SdesPolicy() ==
+              cricket::SEC_REQUIRED);
 }
 
 void PeerConnection::OnTransportControllerGatheringState(
diff --git a/pc/peer_connection_crypto_unittest.cc b/pc/peer_connection_crypto_unittest.cc
index bb66930..c0c3281 100644
--- a/pc/peer_connection_crypto_unittest.cc
+++ b/pc/peer_connection_crypto_unittest.cc
@@ -128,6 +128,13 @@
   };
 }
 
+SdpContentPredicate HaveSdesCryptos() {
+  return [](const cricket::ContentInfo* content,
+            const cricket::TransportInfo* transport) {
+    return !content->media_description()->cryptos().empty();
+  };
+}
+
 SdpContentPredicate HaveProtocol(const std::string& protocol) {
   return [protocol](const cricket::ContentInfo* content,
                     const cricket::TransportInfo* transport) {
@@ -135,6 +142,22 @@
   };
 }
 
+SdpContentPredicate HaveSdesGcmCryptos(size_t num_crypto_suites) {
+  return [num_crypto_suites](const cricket::ContentInfo* content,
+                             const cricket::TransportInfo* transport) {
+    const auto& cryptos = content->media_description()->cryptos();
+    if (cryptos.size() != num_crypto_suites) {
+      return false;
+    }
+    for (size_t i = 0; i < cryptos.size(); ++i) {
+      if (cryptos[i].key_params.size() == 67U &&
+          cryptos[i].cipher_suite == "AEAD_AES_256_GCM")
+        return true;
+    }
+    return false;
+  };
+}
+
 class PeerConnectionCryptoTest
     : public PeerConnectionCryptoBaseTest,
       public ::testing::WithParamInterface<SdpSemantics> {
@@ -142,13 +165,20 @@
   PeerConnectionCryptoTest() : PeerConnectionCryptoBaseTest(GetParam()) {}
 };
 
+SdpContentMutator RemoveSdesCryptos() {
+  return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) {
+    content->media_description()->set_cryptos({});
+  };
+}
+
 SdpContentMutator RemoveDtlsFingerprint() {
   return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) {
     transport->description.identity_fingerprint.reset();
   };
 }
 
-// When DTLS is enabled, the SDP offer/answer should have a DTLS fingerprint.
+// When DTLS is enabled, the SDP offer/answer should have a DTLS fingerprint and
+// no SDES cryptos.
 TEST_P(PeerConnectionCryptoTest, CorrectCryptoInOfferWhenDtlsEnabled) {
   RTCConfiguration config;
   auto caller = CreatePeerConnectionWithAudioVideo(config);
@@ -158,6 +188,7 @@
 
   ASSERT_FALSE(offer->description()->contents().empty());
   EXPECT_TRUE(SdpContentsAll(HaveDtlsFingerprint(), offer->description()));
+  EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), offer->description()));
   EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolDtlsSavpf),
                              offer->description()));
 }
@@ -172,6 +203,7 @@
 
   ASSERT_FALSE(answer->description()->contents().empty());
   EXPECT_TRUE(SdpContentsAll(HaveDtlsFingerprint(), answer->description()));
+  EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), answer->description()));
   EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolDtlsSavpf),
                              answer->description()));
 }
@@ -190,6 +222,7 @@
   ASSERT_TRUE(offer);
 
   ASSERT_FALSE(offer->description()->contents().empty());
+  EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), offer->description()));
   EXPECT_TRUE(SdpContentsNone(HaveDtlsFingerprint(), offer->description()));
   EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolAvpf),
                              offer->description()));
@@ -208,6 +241,7 @@
   ASSERT_TRUE(answer);
 
   ASSERT_FALSE(answer->description()->contents().empty());
+  EXPECT_TRUE(SdpContentsNone(HaveSdesCryptos(), answer->description()));
   EXPECT_TRUE(SdpContentsNone(HaveDtlsFingerprint(), answer->description()));
   EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolAvpf),
                              answer->description()));
diff --git a/pc/peer_connection_interface_unittest.cc b/pc/peer_connection_interface_unittest.cc
index 4731cc8..5452238 100644
--- a/pc/peer_connection_interface_unittest.cc
+++ b/pc/peer_connection_interface_unittest.cc
@@ -2077,13 +2077,10 @@
 #endif
 }
 
-// Test that SDP containing both a=fingerprint and a=crypto is handled
-// by ignoring the a=crypto part.
-// Prior to 2017, such an SDP would be accepted with SDES crypto, but
-// the fallback was removed.
-// Prior to 2021, such an SDP would be rejected because of the mixture.
-// Post 2021, a=crypto lines are totally ignored by the SDP parser.
-TEST_P(PeerConnectionInterfaceTest, SdesIgnored) {
+// Test that fallback from DTLS to SDES is not supported.
+// The fallback was previously supported but was removed to simplify the code
+// and because it's non-standard.
+TEST_P(PeerConnectionInterfaceTest, DtlsSdesFallbackNotSupported) {
   RTCConfiguration rtc_config;
   CreatePeerConnection(rtc_config);
   // Wait for fake certificate to be generated. Previously, this is what caused
@@ -2096,7 +2093,7 @@
   std::unique_ptr<SessionDescriptionInterface> desc(
       webrtc::CreateSessionDescription(SdpType::kOffer, kDtlsSdesFallbackSdp,
                                        nullptr));
-  EXPECT_TRUE(DoSetSessionDescription(std::move(desc), /*local=*/false));
+  EXPECT_FALSE(DoSetSessionDescription(std::move(desc), /*local=*/false));
 }
 
 // Test that we can create an audio only offer and receive an answer with a
diff --git a/pc/peer_connection_signaling_unittest.cc b/pc/peer_connection_signaling_unittest.cc
index 1dbbe7b..13b54d9 100644
--- a/pc/peer_connection_signaling_unittest.cc
+++ b/pc/peer_connection_signaling_unittest.cc
@@ -852,9 +852,6 @@
       "s=-\r\n"
       "t=0 0\r\n"
       "m=bogus 9 FOO 0 8\r\n"
-      "a=fingerprint:sha-256 "
-      "D8:6C:3D:FA:23:E2:2C:63:11:2D:D0:86:BE:C4:D0:65:F9:42:F7:1C:06:04:27:E6:"
-      "1C:2C:74:01:8D:50:67:23\r\n"
       "c=IN IP4 0.0.0.0\r\n"
       "a=mid:bogusmid\r\n";
   std::unique_ptr<webrtc::SessionDescriptionInterface> remote_description =
@@ -864,7 +861,6 @@
 
   // Assert we respond back with something meaningful.
   auto answer = caller->CreateAnswer();
-  ASSERT_TRUE(answer);
   ASSERT_EQ(answer->description()->contents().size(), 1u);
   EXPECT_NE(answer->description()
                 ->contents()[0]
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index 2d0c027..1795cde 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -108,6 +108,7 @@
     "Called with SDP without ice-ufrag and ice-pwd.";
 const char kSdpWithoutDtlsFingerprint[] =
     "Called with SDP without DTLS fingerprint.";
+const char kSdpWithoutSdesCrypto[] = "Called with SDP without SDES crypto.";
 
 const char kSessionError[] = "Session error code: ";
 const char kSessionErrorDesc[] = "Session error description: ";
@@ -345,13 +346,14 @@
                                 const SessionDescription& desc2) {
   return desc1.contents().size() == desc2.contents().size();
 }
-// Checks that each non-rejected content has a DTLS
+// Checks that each non-rejected content has SDES crypto keys or a DTLS
 // fingerprint, unless it's in a BUNDLE group, in which case only the
 // BUNDLE-tag section (first media section/description in the BUNDLE group)
 // needs a ufrag and pwd. Mismatches, such as replying with a DTLS fingerprint
 // to SDES keys, will be caught in JsepTransport negotiation, and backstopped
 // by Channel's `srtp_required` check.
 RTCError VerifyCrypto(const SessionDescription* desc,
+                      bool dtls_enabled,
                       const std::map<std::string, const cricket::ContentGroup*>&
                           bundle_groups_by_mid) {
   for (const cricket::ContentInfo& content_info : desc->contents()) {
@@ -359,8 +361,8 @@
       continue;
     }
     // Note what media is used with each crypto protocol, for all sections.
-    // We now support only DTLS, so this metric can be retired when expiring.
-    NoteKeyProtocolAndMedia(webrtc::kEnumCounterKeyProtocolDtls,
+    NoteKeyProtocolAndMedia(dtls_enabled ? webrtc::kEnumCounterKeyProtocolDtls
+                                         : webrtc::kEnumCounterKeyProtocolSdes,
                             content_info.media_description()->type());
     const std::string& mid = content_info.name;
     auto it = bundle_groups_by_mid.find(mid);
@@ -381,10 +383,20 @@
       // Something is not right.
       LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, kInvalidSdp);
     }
-    if (!tinfo->description.identity_fingerprint) {
-      RTC_LOG(LS_WARNING) << "Session description must have DTLS fingerprint";
-      return RTCError(RTCErrorType::INVALID_PARAMETER,
-                      kSdpWithoutDtlsFingerprint);
+    if (dtls_enabled) {
+      if (!tinfo->description.identity_fingerprint) {
+        RTC_LOG(LS_WARNING)
+            << "Session description must have DTLS fingerprint if "
+               "DTLS enabled.";
+        return RTCError(RTCErrorType::INVALID_PARAMETER,
+                        kSdpWithoutDtlsFingerprint);
+      }
+    } else {
+      if (media->cryptos().empty()) {
+        RTC_LOG(LS_WARNING)
+            << "Session description must have SDES when DTLS disabled.";
+        return RTCError(RTCErrorType::INVALID_PARAMETER, kSdpWithoutSdesCrypto);
+      }
     }
   }
   return RTCError::OK();
@@ -988,6 +1000,10 @@
             transport_controller()->SetLocalCertificate(certificate);
           });
 
+  if (pc_->options()->disable_encryption) {
+    webrtc_session_desc_factory_->SetSdesPolicy(cricket::SEC_DISABLED);
+  }
+
   webrtc_session_desc_factory_->set_enable_encrypted_rtp_header_extensions(
       pc_->GetCryptoOptions().srtp.enable_encrypted_rtp_header_extensions);
   webrtc_session_desc_factory_->set_is_unified_plan(IsUnifiedPlan());
@@ -3025,9 +3041,10 @@
 
   // Verify crypto settings.
   std::string crypto_error;
-  if (pc_->dtls_enabled()) {
-    RTCError crypto_error =
-        VerifyCrypto(sdesc->description(), bundle_groups_by_mid);
+  if (webrtc_session_desc_factory_->SdesPolicy() == cricket::SEC_REQUIRED ||
+      pc_->dtls_enabled()) {
+    RTCError crypto_error = VerifyCrypto(
+        sdesc->description(), pc_->dtls_enabled(), bundle_groups_by_mid);
     if (!crypto_error.ok()) {
       return crypto_error;
     }
diff --git a/pc/session_description.h b/pc/session_description.h
index db393ab..ee7a91c 100644
--- a/pc/session_description.h
+++ b/pc/session_description.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "absl/memory/memory.h"
+#include "api/crypto_params.h"
 #include "api/media_types.h"
 #include "api/rtp_parameters.h"
 #include "api/rtp_transceiver_direction.h"
@@ -43,6 +44,7 @@
 
 typedef std::vector<AudioCodec> AudioCodecs;
 typedef std::vector<VideoCodec> VideoCodecs;
+typedef std::vector<CryptoParams> CryptoParamsVec;
 typedef std::vector<webrtc::RtpExtension> RtpHeaderExtensions;
 
 // Options to control how session descriptions are generated.
@@ -126,6 +128,14 @@
     bandwidth_type_ = bandwidth_type;
   }
 
+  virtual const std::vector<CryptoParams>& cryptos() const { return cryptos_; }
+  virtual void AddCrypto(const CryptoParams& params) {
+    cryptos_.push_back(params);
+  }
+  virtual void set_cryptos(const std::vector<CryptoParams>& cryptos) {
+    cryptos_ = cryptos;
+  }
+
   // List of RTP header extensions. URIs are **NOT** guaranteed to be unique
   // as they can appear twice when both encrypted and non-encrypted extensions
   // are present.
@@ -249,6 +259,7 @@
   int bandwidth_ = kAutoBandwidth;
   std::string bandwidth_type_ = kApplicationSpecificBandwidth;
   std::string protocol_;
+  std::vector<CryptoParams> cryptos_;
   std::vector<webrtc::RtpExtension> rtp_header_extensions_;
   bool rtp_header_extensions_set_ = false;
   StreamParamsVec send_streams_;
diff --git a/pc/srtp_filter.cc b/pc/srtp_filter.cc
new file mode 100644
index 0000000..c48dfdb
--- /dev/null
+++ b/pc/srtp_filter.cc
@@ -0,0 +1,280 @@
+/*
+ *  Copyright 2009 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "pc/srtp_filter.h"
+
+#include <string.h>
+#include <cstdint>
+#include <memory>
+
+#include "absl/strings/match.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/ssl_stream_adapter.h"
+#include "rtc_base/third_party/base64/base64.h"
+#include "rtc_base/zero_memory.h"
+
+namespace cricket {
+
+SrtpFilter::SrtpFilter() {}
+
+SrtpFilter::~SrtpFilter() {}
+
+bool SrtpFilter::IsActive() const {
+  return state_ >= ST_ACTIVE;
+}
+
+bool SrtpFilter::Process(const std::vector<CryptoParams>& cryptos,
+                         webrtc::SdpType type,
+                         ContentSource source) {
+  bool ret = false;
+  switch (type) {
+    case webrtc::SdpType::kOffer:
+      ret = SetOffer(cryptos, source);
+      break;
+    case webrtc::SdpType::kPrAnswer:
+      ret = SetProvisionalAnswer(cryptos, source);
+      break;
+    case webrtc::SdpType::kAnswer:
+      ret = SetAnswer(cryptos, source);
+      break;
+    default:
+      break;
+  }
+
+  if (!ret) {
+    return false;
+  }
+
+  return true;
+}
+
+bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
+                          ContentSource source) {
+  if (!ExpectOffer(source)) {
+    RTC_LOG(LS_ERROR) << "Wrong state to update SRTP offer";
+    return false;
+  }
+  return StoreParams(offer_params, source);
+}
+
+bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
+                           ContentSource source) {
+  return DoSetAnswer(answer_params, source, true);
+}
+
+bool SrtpFilter::SetProvisionalAnswer(
+    const std::vector<CryptoParams>& answer_params,
+    ContentSource source) {
+  return DoSetAnswer(answer_params, source, false);
+}
+
+bool SrtpFilter::ExpectOffer(ContentSource source) {
+  return ((state_ == ST_INIT) || (state_ == ST_ACTIVE) ||
+          (state_ == ST_SENTOFFER && source == CS_LOCAL) ||
+          (state_ == ST_SENTUPDATEDOFFER && source == CS_LOCAL) ||
+          (state_ == ST_RECEIVEDOFFER && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_REMOTE));
+}
+
+bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
+                             ContentSource source) {
+  offer_params_ = params;
+  if (state_ == ST_INIT) {
+    state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+  } else if (state_ == ST_ACTIVE) {
+    state_ =
+        (source == CS_LOCAL) ? ST_SENTUPDATEDOFFER : ST_RECEIVEDUPDATEDOFFER;
+  }
+  return true;
+}
+
+bool SrtpFilter::ExpectAnswer(ContentSource source) {
+  return ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL) ||
+          (state_ == ST_SENTUPDATEDOFFER && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_LOCAL) ||
+          (state_ == ST_SENTPRANSWER_NO_CRYPTO && source == CS_LOCAL) ||
+          (state_ == ST_SENTPRANSWER && source == CS_LOCAL) ||
+          (state_ == ST_RECEIVEDPRANSWER_NO_CRYPTO && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDPRANSWER && source == CS_REMOTE));
+}
+
+bool SrtpFilter::DoSetAnswer(const std::vector<CryptoParams>& answer_params,
+                             ContentSource source,
+                             bool final) {
+  if (!ExpectAnswer(source)) {
+    RTC_LOG(LS_ERROR) << "Invalid state for SRTP answer";
+    return false;
+  }
+
+  // If the answer doesn't requests crypto complete the negotiation of an
+  // unencrypted session.
+  // Otherwise, finalize the parameters and apply them.
+  if (answer_params.empty()) {
+    if (final) {
+      return ResetParams();
+    } else {
+      // Need to wait for the final answer to decide if
+      // we should go to Active state.
+      state_ = (source == CS_LOCAL) ? ST_SENTPRANSWER_NO_CRYPTO
+                                    : ST_RECEIVEDPRANSWER_NO_CRYPTO;
+      return true;
+    }
+  }
+  CryptoParams selected_params;
+  if (!NegotiateParams(answer_params, &selected_params))
+    return false;
+
+  const CryptoParams& new_send_params =
+      (source == CS_REMOTE) ? selected_params : answer_params[0];
+  const CryptoParams& new_recv_params =
+      (source == CS_REMOTE) ? answer_params[0] : selected_params;
+  if (!ApplySendParams(new_send_params) || !ApplyRecvParams(new_recv_params)) {
+    return false;
+  }
+  applied_send_params_ = new_send_params;
+  applied_recv_params_ = new_recv_params;
+
+  if (final) {
+    offer_params_.clear();
+    state_ = ST_ACTIVE;
+  } else {
+    state_ = (source == CS_LOCAL) ? ST_SENTPRANSWER : ST_RECEIVEDPRANSWER;
+  }
+  return true;
+}
+
+bool SrtpFilter::NegotiateParams(const std::vector<CryptoParams>& answer_params,
+                                 CryptoParams* selected_params) {
+  // We're processing an accept. We should have exactly one set of params,
+  // unless the offer didn't mention crypto, in which case we shouldn't be here.
+  bool ret = (answer_params.size() == 1U && !offer_params_.empty());
+  if (ret) {
+    // We should find a match between the answer params and the offered params.
+    std::vector<CryptoParams>::const_iterator it;
+    for (it = offer_params_.begin(); it != offer_params_.end(); ++it) {
+      if (answer_params[0].Matches(*it)) {
+        break;
+      }
+    }
+
+    if (it != offer_params_.end()) {
+      *selected_params = *it;
+    } else {
+      ret = false;
+    }
+  }
+
+  if (!ret) {
+    RTC_LOG(LS_WARNING) << "Invalid parameters in SRTP answer";
+  }
+  return ret;
+}
+
+bool SrtpFilter::ResetParams() {
+  offer_params_.clear();
+  applied_send_params_ = CryptoParams();
+  applied_recv_params_ = CryptoParams();
+  send_cipher_suite_ = absl::nullopt;
+  recv_cipher_suite_ = absl::nullopt;
+  send_key_.Clear();
+  recv_key_.Clear();
+  state_ = ST_INIT;
+  return true;
+}
+
+bool SrtpFilter::ApplySendParams(const CryptoParams& send_params) {
+  if (applied_send_params_.cipher_suite == send_params.cipher_suite &&
+      applied_send_params_.key_params == send_params.key_params) {
+    RTC_LOG(LS_INFO) << "Applying the same SRTP send parameters again. No-op.";
+
+    // We do not want to reset the ROC if the keys are the same. So just return.
+    return true;
+  }
+
+  send_cipher_suite_ = rtc::SrtpCryptoSuiteFromName(send_params.cipher_suite);
+  if (send_cipher_suite_ == rtc::kSrtpInvalidCryptoSuite) {
+    RTC_LOG(LS_WARNING) << "Unknown crypto suite(s) received:"
+                           " send cipher_suite "
+                        << send_params.cipher_suite;
+    return false;
+  }
+
+  int send_key_len, send_salt_len;
+  if (!rtc::GetSrtpKeyAndSaltLengths(*send_cipher_suite_, &send_key_len,
+                                     &send_salt_len)) {
+    RTC_LOG(LS_ERROR) << "Could not get lengths for crypto suite(s):"
+                         " send cipher_suite "
+                      << send_params.cipher_suite;
+    return false;
+  }
+
+  send_key_ = rtc::ZeroOnFreeBuffer<uint8_t>(send_key_len + send_salt_len);
+  return ParseKeyParams(send_params.key_params, send_key_.data(),
+                        send_key_.size());
+}
+
+bool SrtpFilter::ApplyRecvParams(const CryptoParams& recv_params) {
+  if (applied_recv_params_.cipher_suite == recv_params.cipher_suite &&
+      applied_recv_params_.key_params == recv_params.key_params) {
+    RTC_LOG(LS_INFO) << "Applying the same SRTP recv parameters again. No-op.";
+
+    // We do not want to reset the ROC if the keys are the same. So just return.
+    return true;
+  }
+
+  recv_cipher_suite_ = rtc::SrtpCryptoSuiteFromName(recv_params.cipher_suite);
+  if (recv_cipher_suite_ == rtc::kSrtpInvalidCryptoSuite) {
+    RTC_LOG(LS_WARNING) << "Unknown crypto suite(s) received:"
+                           " recv cipher_suite "
+                        << recv_params.cipher_suite;
+    return false;
+  }
+
+  int recv_key_len, recv_salt_len;
+  if (!rtc::GetSrtpKeyAndSaltLengths(*recv_cipher_suite_, &recv_key_len,
+                                     &recv_salt_len)) {
+    RTC_LOG(LS_ERROR) << "Could not get lengths for crypto suite(s):"
+                         " recv cipher_suite "
+                      << recv_params.cipher_suite;
+    return false;
+  }
+
+  recv_key_ = rtc::ZeroOnFreeBuffer<uint8_t>(recv_key_len + recv_salt_len);
+  return ParseKeyParams(recv_params.key_params, recv_key_.data(),
+                        recv_key_.size());
+}
+
+bool SrtpFilter::ParseKeyParams(const std::string& key_params,
+                                uint8_t* key,
+                                size_t len) {
+  // example key_params: "inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2"
+
+  // Fail if key-method is wrong.
+  if (!absl::StartsWith(key_params, "inline:")) {
+    return false;
+  }
+
+  // Fail if base64 decode fails, or the key is the wrong size.
+  std::string key_b64(key_params.substr(7)), key_str;
+  if (!rtc::Base64::Decode(key_b64, rtc::Base64::DO_STRICT, &key_str,
+                           nullptr) ||
+      key_str.size() != len) {
+    return false;
+  }
+
+  memcpy(key, key_str.c_str(), len);
+  // TODO(bugs.webrtc.org/8905): Switch to ZeroOnFreeBuffer for storing
+  // sensitive data.
+  rtc::ExplicitZeroMemory(&key_str[0], key_str.size());
+  return true;
+}
+
+}  // namespace cricket
diff --git a/pc/srtp_filter.h b/pc/srtp_filter.h
new file mode 100644
index 0000000..f1e1649
--- /dev/null
+++ b/pc/srtp_filter.h
@@ -0,0 +1,148 @@
+/*
+ *  Copyright 2009 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_SRTP_FILTER_H_
+#define PC_SRTP_FILTER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "api/crypto_params.h"
+#include "api/jsep.h"
+#include "api/sequence_checker.h"
+#include "pc/session_description.h"
+#include "rtc_base/buffer.h"
+#include "rtc_base/constructor_magic.h"
+#include "rtc_base/ssl_stream_adapter.h"
+
+// Forward declaration to avoid pulling in libsrtp headers here
+struct srtp_event_data_t;
+struct srtp_ctx_t_;
+
+namespace cricket {
+
+// A helper class used to negotiate SDES crypto params.
+// TODO(zhihuang): Find a better name for this class, like "SdesNegotiator".
+class SrtpFilter {
+ public:
+  enum Mode { PROTECT, UNPROTECT };
+  enum Error {
+    ERROR_NONE,
+    ERROR_FAIL,
+    ERROR_AUTH,
+    ERROR_REPLAY,
+  };
+
+  SrtpFilter();
+  ~SrtpFilter();
+
+  // Whether the filter is active (i.e. crypto has been properly negotiated).
+  bool IsActive() const;
+
+  // Handle the offer/answer negotiation of the crypto parameters internally.
+  // TODO(zhihuang): Make SetOffer/ProvisionalAnswer/Answer private as helper
+  // methods once start using Process.
+  bool Process(const std::vector<CryptoParams>& cryptos,
+               webrtc::SdpType type,
+               ContentSource source);
+
+  // Indicates which crypto algorithms and keys were contained in the offer.
+  // offer_params should contain a list of available parameters to use, or none,
+  // if crypto is not desired. This must be called before SetAnswer.
+  bool SetOffer(const std::vector<CryptoParams>& offer_params,
+                ContentSource source);
+  // Same as SetAnwer. But multiple calls are allowed to SetProvisionalAnswer
+  // after a call to SetOffer.
+  bool SetProvisionalAnswer(const std::vector<CryptoParams>& answer_params,
+                            ContentSource source);
+  // Indicates which crypto algorithms and keys were contained in the answer.
+  // answer_params should contain the negotiated parameters, which may be none,
+  // if crypto was not desired or could not be negotiated (and not required).
+  // This must be called after SetOffer. If crypto negotiation completes
+  // successfully, this will advance the filter to the active state.
+  bool SetAnswer(const std::vector<CryptoParams>& answer_params,
+                 ContentSource source);
+
+  bool ResetParams();
+
+  static bool ParseKeyParams(const std::string& params,
+                             uint8_t* key,
+                             size_t len);
+
+  absl::optional<int> send_cipher_suite() { return send_cipher_suite_; }
+  absl::optional<int> recv_cipher_suite() { return recv_cipher_suite_; }
+
+  rtc::ArrayView<const uint8_t> send_key() { return send_key_; }
+  rtc::ArrayView<const uint8_t> recv_key() { return recv_key_; }
+
+ protected:
+  bool ExpectOffer(ContentSource source);
+
+  bool StoreParams(const std::vector<CryptoParams>& params,
+                   ContentSource source);
+
+  bool ExpectAnswer(ContentSource source);
+
+  bool DoSetAnswer(const std::vector<CryptoParams>& answer_params,
+                   ContentSource source,
+                   bool final);
+
+  bool NegotiateParams(const std::vector<CryptoParams>& answer_params,
+                       CryptoParams* selected_params);
+
+ private:
+  bool ApplySendParams(const CryptoParams& send_params);
+
+  bool ApplyRecvParams(const CryptoParams& recv_params);
+
+  enum State {
+    ST_INIT,                    // SRTP filter unused.
+    ST_SENTOFFER,               // Offer with SRTP parameters sent.
+    ST_RECEIVEDOFFER,           // Offer with SRTP parameters received.
+    ST_SENTPRANSWER_NO_CRYPTO,  // Sent provisional answer without crypto.
+    // Received provisional answer without crypto.
+    ST_RECEIVEDPRANSWER_NO_CRYPTO,
+    ST_ACTIVE,  // Offer and answer set.
+    // SRTP filter is active but new parameters are offered.
+    // When the answer is set, the state transitions to ST_ACTIVE or ST_INIT.
+    ST_SENTUPDATEDOFFER,
+    // SRTP filter is active but new parameters are received.
+    // When the answer is set, the state transitions back to ST_ACTIVE.
+    ST_RECEIVEDUPDATEDOFFER,
+    // SRTP filter is active but the sent answer is only provisional.
+    // When the final answer is set, the state transitions to ST_ACTIVE or
+    // ST_INIT.
+    ST_SENTPRANSWER,
+    // SRTP filter is active but the received answer is only provisional.
+    // When the final answer is set, the state transitions to ST_ACTIVE or
+    // ST_INIT.
+    ST_RECEIVEDPRANSWER
+  };
+  State state_ = ST_INIT;
+  std::vector<CryptoParams> offer_params_;
+  CryptoParams applied_send_params_;
+  CryptoParams applied_recv_params_;
+  absl::optional<int> send_cipher_suite_;
+  absl::optional<int> recv_cipher_suite_;
+  rtc::ZeroOnFreeBuffer<uint8_t> send_key_;
+  rtc::ZeroOnFreeBuffer<uint8_t> recv_key_;
+};
+
+}  // namespace cricket
+
+#endif  // PC_SRTP_FILTER_H_
diff --git a/pc/srtp_filter_unittest.cc b/pc/srtp_filter_unittest.cc
new file mode 100644
index 0000000..eadaad6
--- /dev/null
+++ b/pc/srtp_filter_unittest.cc
@@ -0,0 +1,472 @@
+/*
+ *  Copyright 2004 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "pc/srtp_filter.h"
+
+#include <string.h>
+
+#include "api/crypto_params.h"
+#include "rtc_base/ssl_stream_adapter.h"
+#include "test/gtest.h"
+
+using cricket::CryptoParams;
+using cricket::CS_LOCAL;
+using cricket::CS_REMOTE;
+
+namespace rtc {
+
+static const char kTestKeyParams1[] =
+    "inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz";
+static const char kTestKeyParams2[] =
+    "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+static const char kTestKeyParams3[] =
+    "inline:1234X19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz";
+static const char kTestKeyParams4[] =
+    "inline:4567QCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+static const char kTestKeyParamsGcm1[] =
+    "inline:e166KFlKzJsGW0d5apX+rrI05vxbrvMJEzFI14aTDCa63IRTlLK4iH66uOI=";
+static const char kTestKeyParamsGcm2[] =
+    "inline:6X0oCd55zfz4VgtOwsuqcFq61275PDYN5uwuu3p7ZUHbfUY2FMpdP4m2PEo=";
+static const char kTestKeyParamsGcm3[] =
+    "inline:YKlABGZWMgX32xuMotrG0v0T7G83veegaVzubQ==";
+static const char kTestKeyParamsGcm4[] =
+    "inline:gJ6tWoUym2v+/F6xjr7xaxiS3QbJJozl3ZD/0A==";
+static const cricket::CryptoParams kTestCryptoParams1(1,
+                                                      "AES_CM_128_HMAC_SHA1_80",
+                                                      kTestKeyParams1,
+                                                      "");
+static const cricket::CryptoParams kTestCryptoParams2(1,
+                                                      "AES_CM_128_HMAC_SHA1_80",
+                                                      kTestKeyParams2,
+                                                      "");
+static const cricket::CryptoParams kTestCryptoParamsGcm1(1,
+                                                         "AEAD_AES_256_GCM",
+                                                         kTestKeyParamsGcm1,
+                                                         "");
+static const cricket::CryptoParams kTestCryptoParamsGcm2(1,
+                                                         "AEAD_AES_256_GCM",
+                                                         kTestKeyParamsGcm2,
+                                                         "");
+static const cricket::CryptoParams kTestCryptoParamsGcm3(1,
+                                                         "AEAD_AES_128_GCM",
+                                                         kTestKeyParamsGcm3,
+                                                         "");
+static const cricket::CryptoParams kTestCryptoParamsGcm4(1,
+                                                         "AEAD_AES_128_GCM",
+                                                         kTestKeyParamsGcm4,
+                                                         "");
+
+class SrtpFilterTest : public ::testing::Test {
+ protected:
+  SrtpFilterTest() {}
+  static std::vector<CryptoParams> MakeVector(const CryptoParams& params) {
+    std::vector<CryptoParams> vec;
+    vec.push_back(params);
+    return vec;
+  }
+
+  void TestSetParams(const std::vector<CryptoParams>& params1,
+                     const std::vector<CryptoParams>& params2) {
+    EXPECT_TRUE(f1_.SetOffer(params1, CS_LOCAL));
+    EXPECT_TRUE(f2_.SetOffer(params1, CS_REMOTE));
+    EXPECT_FALSE(f1_.IsActive());
+    EXPECT_FALSE(f2_.IsActive());
+    EXPECT_TRUE(f2_.SetAnswer(params2, CS_LOCAL));
+    EXPECT_TRUE(f1_.SetAnswer(params2, CS_REMOTE));
+    EXPECT_TRUE(f1_.IsActive());
+    EXPECT_TRUE(f2_.IsActive());
+  }
+
+  void VerifyKeysAreEqual(ArrayView<const uint8_t> key1,
+                          ArrayView<const uint8_t> key2) {
+    EXPECT_EQ(key1.size(), key2.size());
+    EXPECT_EQ(0, memcmp(key1.data(), key2.data(), key1.size()));
+  }
+
+  void VerifyCryptoParamsMatch(const std::string& cs1, const std::string& cs2) {
+    EXPECT_EQ(rtc::SrtpCryptoSuiteFromName(cs1), f1_.send_cipher_suite());
+    EXPECT_EQ(rtc::SrtpCryptoSuiteFromName(cs2), f2_.send_cipher_suite());
+    VerifyKeysAreEqual(f1_.send_key(), f2_.recv_key());
+    VerifyKeysAreEqual(f2_.send_key(), f1_.recv_key());
+  }
+
+  cricket::SrtpFilter f1_;
+  cricket::SrtpFilter f2_;
+};
+
+// Test that we can set up the session and keys properly.
+TEST_F(SrtpFilterTest, TestGoodSetupOneCipherSuite) {
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+}
+
+TEST_F(SrtpFilterTest, TestGoodSetupOneCipherSuiteGcm) {
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParamsGcm1), CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParamsGcm2), CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+}
+
+// Test that we can set up things with multiple params.
+TEST_F(SrtpFilterTest, TestGoodSetupMultipleCipherSuites) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = kCsAesCm128HmacSha1_32;
+  answer[0].tag = 2;
+  answer[0].cipher_suite = kCsAesCm128HmacSha1_32;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+}
+
+TEST_F(SrtpFilterTest, TestGoodSetupMultipleCipherSuitesGcm) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParamsGcm1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParamsGcm3));
+  offer.push_back(kTestCryptoParamsGcm4);
+  offer[1].tag = 2;
+  answer[0].tag = 2;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+}
+
+// Test that we handle the cases where crypto is not desired.
+TEST_F(SrtpFilterTest, TestGoodSetupNoCipherSuites) {
+  std::vector<CryptoParams> offer, answer;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we handle the cases where crypto is not desired by the remote side.
+TEST_F(SrtpFilterTest, TestGoodSetupNoAnswerCipherSuites) {
+  std::vector<CryptoParams> answer;
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we call the functions the wrong way.
+TEST_F(SrtpFilterTest, TestBadSetup) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we can set offer multiple times from the same source.
+TEST_F(SrtpFilterTest, TestGoodSetupMultipleOffers) {
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_TRUE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+}
+// Test that we can't set offer multiple times from different sources.
+TEST_F(SrtpFilterTest, TestBadSetupMultipleOffers) {
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_FALSE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_FALSE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_FALSE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_TRUE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+}
+
+// Test that we fail if we have params in the answer when none were offered.
+TEST_F(SrtpFilterTest, TestNoAnswerCipherSuites) {
+  std::vector<CryptoParams> offer;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we have too many params in our answer.
+TEST_F(SrtpFilterTest, TestMultipleAnswerCipherSuites) {
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer.push_back(kTestCryptoParams2);
+  answer[1].tag = 2;
+  answer[1].cipher_suite = kCsAesCm128HmacSha1_32;
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we don't support the cipher-suite.
+TEST_F(SrtpFilterTest, TestInvalidCipherSuite) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer[0].cipher_suite = answer[0].cipher_suite = "FOO";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we can't agree on a tag.
+TEST_F(SrtpFilterTest, TestNoMatchingTag) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].tag = 99;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we can't agree on a cipher-suite.
+TEST_F(SrtpFilterTest, TestNoMatchingCipherSuite) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].tag = 2;
+  answer[0].cipher_suite = "FOO";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with bad base64 content.
+TEST_F(SrtpFilterTest, TestInvalidKeyData) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "inline:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with the wrong key-method.
+TEST_F(SrtpFilterTest, TestWrongKeyMethod) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "outline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpFilterTest, TestKeyTooShort) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtx";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpFilterTest, TestKeyTooLong) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBRABCD";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with lifetime or MKI set (since we don't support)
+TEST_F(SrtpFilterTest, TestUnsupportedOptions) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params =
+      "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:4";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we can encrypt/decrypt after negotiating AES_CM_128_HMAC_SHA1_80.
+TEST_F(SrtpFilterTest, TestProtect_AES_CM_128_HMAC_SHA1_80) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = kCsAesCm128HmacSha1_32;
+  TestSetParams(offer, answer);
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+}
+
+// Test that we can encrypt/decrypt after negotiating AES_CM_128_HMAC_SHA1_32.
+TEST_F(SrtpFilterTest, TestProtect_AES_CM_128_HMAC_SHA1_32) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = kCsAesCm128HmacSha1_32;
+  answer[0].tag = 2;
+  answer[0].cipher_suite = kCsAesCm128HmacSha1_32;
+  TestSetParams(offer, answer);
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_32, kCsAesCm128HmacSha1_32);
+}
+
+// Test that we can change encryption parameters.
+TEST_F(SrtpFilterTest, TestChangeParameters) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+  TestSetParams(offer, answer);
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+
+  // Change the key parameters and cipher_suite.
+  offer[0].key_params = kTestKeyParams3;
+  offer[0].cipher_suite = kCsAesCm128HmacSha1_32;
+  answer[0].key_params = kTestKeyParams4;
+  answer[0].cipher_suite = kCsAesCm128HmacSha1_32;
+
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f1_.IsActive());
+
+  // Test that the old keys are valid until the negotiation is complete.
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+
+  // Complete the negotiation and test that we can still understand each other.
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_32, kCsAesCm128HmacSha1_32);
+}
+
+// Test that we can send and receive provisional answers with crypto enabled.
+// Also test that we can change the crypto.
+TEST_F(SrtpFilterTest, TestProvisionalAnswer) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = kCsAesCm128HmacSha1_32;
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetProvisionalAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetProvisionalAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+
+  answer[0].key_params = kTestKeyParams4;
+  answer[0].tag = 2;
+  answer[0].cipher_suite = kCsAesCm128HmacSha1_32;
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_32, kCsAesCm128HmacSha1_32);
+}
+
+// Test that a provisional answer doesn't need to contain a crypto.
+TEST_F(SrtpFilterTest, TestProvisionalAnswerWithoutCrypto) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer;
+
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetProvisionalAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetProvisionalAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+
+  answer.push_back(kTestCryptoParams2);
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+}
+
+// Test that if we get a new local offer after a provisional answer
+// with no crypto, that we are in an inactive state.
+TEST_F(SrtpFilterTest, TestLocalOfferAfterProvisionalAnswerWithoutCrypto) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer;
+
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f1_.SetProvisionalAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f2_.SetProvisionalAnswer(answer, CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+  // The calls to set an offer after a provisional answer fail, so the
+  // state doesn't change.
+  EXPECT_FALSE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+
+  answer.push_back(kTestCryptoParams2);
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+}
+
+// Test that we can disable encryption.
+TEST_F(SrtpFilterTest, TestDisableEncryption) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+  TestSetParams(offer, answer);
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+
+  offer.clear();
+  answer.clear();
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+
+  // Test that the old keys are valid until the negotiation is complete.
+  VerifyCryptoParamsMatch(kCsAesCm128HmacSha1_80, kCsAesCm128HmacSha1_80);
+
+  // Complete the negotiation.
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+}
+
+}  // namespace rtc
diff --git a/pc/srtp_transport.cc b/pc/srtp_transport.cc
index fa2fc3b..230c1a3 100644
--- a/pc/srtp_transport.cc
+++ b/pc/srtp_transport.cc
@@ -37,6 +37,86 @@
 SrtpTransport::SrtpTransport(bool rtcp_mux_enabled)
     : RtpTransport(rtcp_mux_enabled) {}
 
+RTCError SrtpTransport::SetSrtpSendKey(const cricket::CryptoParams& params) {
+  if (send_params_) {
+    LOG_AND_RETURN_ERROR(
+        webrtc::RTCErrorType::UNSUPPORTED_OPERATION,
+        "Setting the SRTP send key twice is currently unsupported.");
+  }
+  if (recv_params_ && recv_params_->cipher_suite != params.cipher_suite) {
+    LOG_AND_RETURN_ERROR(
+        webrtc::RTCErrorType::UNSUPPORTED_OPERATION,
+        "The send key and receive key must have the same cipher suite.");
+  }
+
+  send_cipher_suite_ = rtc::SrtpCryptoSuiteFromName(params.cipher_suite);
+  if (*send_cipher_suite_ == rtc::kSrtpInvalidCryptoSuite) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Invalid SRTP crypto suite");
+  }
+
+  int send_key_len, send_salt_len;
+  if (!rtc::GetSrtpKeyAndSaltLengths(*send_cipher_suite_, &send_key_len,
+                                     &send_salt_len)) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Could not get lengths for crypto suite(s):"
+                    " send cipher_suite ");
+  }
+
+  send_key_ = rtc::ZeroOnFreeBuffer<uint8_t>(send_key_len + send_salt_len);
+  if (!ParseKeyParams(params.key_params, send_key_.data(), send_key_.size())) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Failed to parse the crypto key params");
+  }
+
+  if (!MaybeSetKeyParams()) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Failed to set the crypto key params");
+  }
+  send_params_ = params;
+  return RTCError::OK();
+}
+
+RTCError SrtpTransport::SetSrtpReceiveKey(const cricket::CryptoParams& params) {
+  if (recv_params_) {
+    LOG_AND_RETURN_ERROR(
+        webrtc::RTCErrorType::UNSUPPORTED_OPERATION,
+        "Setting the SRTP send key twice is currently unsupported.");
+  }
+  if (send_params_ && send_params_->cipher_suite != params.cipher_suite) {
+    LOG_AND_RETURN_ERROR(
+        webrtc::RTCErrorType::UNSUPPORTED_OPERATION,
+        "The send key and receive key must have the same cipher suite.");
+  }
+
+  recv_cipher_suite_ = rtc::SrtpCryptoSuiteFromName(params.cipher_suite);
+  if (*recv_cipher_suite_ == rtc::kSrtpInvalidCryptoSuite) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Invalid SRTP crypto suite");
+  }
+
+  int recv_key_len, recv_salt_len;
+  if (!rtc::GetSrtpKeyAndSaltLengths(*recv_cipher_suite_, &recv_key_len,
+                                     &recv_salt_len)) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Could not get lengths for crypto suite(s):"
+                    " recv cipher_suite ");
+  }
+
+  recv_key_ = rtc::ZeroOnFreeBuffer<uint8_t>(recv_key_len + recv_salt_len);
+  if (!ParseKeyParams(params.key_params, recv_key_.data(), recv_key_.size())) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Failed to parse the crypto key params");
+  }
+
+  if (!MaybeSetKeyParams()) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Failed to set the crypto key params");
+  }
+  recv_params_ = params;
+  return RTCError::OK();
+}
+
 bool SrtpTransport::SendRtpPacket(rtc::CopyOnWriteBuffer* packet,
                                   const rtc::PacketOptions& options,
                                   int flags) {
diff --git a/pc/srtp_transport.h b/pc/srtp_transport.h
index 03fb146..4bc028d 100644
--- a/pc/srtp_transport.h
+++ b/pc/srtp_transport.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "absl/types/optional.h"
+#include "api/crypto_params.h"
 #include "api/rtc_error.h"
 #include "p2p/base/packet_transport_internal.h"
 #include "pc/rtp_transport.h"
@@ -39,6 +40,10 @@
 
   virtual ~SrtpTransport() = default;
 
+  // SrtpTransportInterface specific implementation.
+  virtual RTCError SetSrtpSendKey(const cricket::CryptoParams& params);
+  virtual RTCError SetSrtpReceiveKey(const cricket::CryptoParams& params);
+
   bool SendRtpPacket(rtc::CopyOnWriteBuffer* packet,
                      const rtc::PacketOptions& options,
                      int flags) override;
@@ -148,6 +153,8 @@
   std::unique_ptr<cricket::SrtpSession> send_rtcp_session_;
   std::unique_ptr<cricket::SrtpSession> recv_rtcp_session_;
 
+  absl::optional<cricket::CryptoParams> send_params_;
+  absl::optional<cricket::CryptoParams> recv_params_;
   absl::optional<int> send_cipher_suite_;
   absl::optional<int> recv_cipher_suite_;
   rtc::ZeroOnFreeBuffer<uint8_t> send_key_;
diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc
index ba58f43..6def54a 100644
--- a/pc/webrtc_sdp.cc
+++ b/pc/webrtc_sdp.cc
@@ -26,6 +26,7 @@
 
 #include "absl/algorithm/container.h"
 #include "api/candidate.h"
+#include "api/crypto_params.h"
 #include "api/jsep_ice_candidate.h"
 #include "api/jsep_session_description.h"
 #include "api/media_types.h"
@@ -69,6 +70,7 @@
 using cricket::Candidate;
 using cricket::Candidates;
 using cricket::ContentInfo;
+using cricket::CryptoParams;
 using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
 using cricket::ICE_CANDIDATE_COMPONENT_RTP;
 using cricket::kApplicationSpecificBandwidth;
@@ -152,6 +154,7 @@
 static const char kSsrcAttributeMslabel[] = "mslabel";
 static const char kSSrcAttributeLabel[] = "label";
 static const char kAttributeSsrcGroup[] = "ssrc-group";
+static const char kAttributeCrypto[] = "crypto";
 static const char kAttributeCandidate[] = "candidate";
 static const char kAttributeCandidateTyp[] = "typ";
 static const char kAttributeCandidateRaddr[] = "raddr";
@@ -329,6 +332,9 @@
 static bool ParseSsrcGroupAttribute(const std::string& line,
                                     SsrcGroupVec* ssrc_groups,
                                     SdpParseError* error);
+static bool ParseCryptoAttribute(const std::string& line,
+                                 MediaContentDescription* media_desc,
+                                 SdpParseError* error);
 static bool ParseRtpmapAttribute(const std::string& line,
                                  const cricket::MediaType media_type,
                                  const std::vector<int>& payload_types,
@@ -1671,6 +1677,18 @@
     AddLine(os.str(), message);
   }
 
+  // RFC 4568
+  // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+  for (const CryptoParams& crypto_params : media_desc->cryptos()) {
+    InitAttrLine(kAttributeCrypto, &os);
+    os << kSdpDelimiterColon << crypto_params.tag << " "
+       << crypto_params.cipher_suite << " " << crypto_params.key_params;
+    if (!crypto_params.session_params.empty()) {
+      os << " " << crypto_params.session_params;
+    }
+    AddLine(os.str(), message);
+  }
+
   // RFC 4566
   // a=rtpmap:<payload type> <encoding name>/<clock rate>
   // [/<encodingparameters>]
@@ -3152,6 +3170,10 @@
         if (!ParseSsrcAttribute(line, &ssrc_infos, msid_signaling, error)) {
           return false;
         }
+      } else if (HasAttribute(line, kAttributeCrypto)) {
+        if (!ParseCryptoAttribute(line, media_desc, error)) {
+          return false;
+        }
       } else if (HasAttribute(line, kAttributeRtpmap)) {
         if (!ParseRtpmapAttribute(line, media_type, payload_types, media_desc,
                                   error)) {
@@ -3481,6 +3503,36 @@
   return true;
 }
 
+bool ParseCryptoAttribute(const std::string& line,
+                          MediaContentDescription* media_desc,
+                          SdpParseError* error) {
+  std::vector<std::string> fields;
+  rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar, &fields);
+  // RFC 4568
+  // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+  const size_t expected_min_fields = 3;
+  if (fields.size() < expected_min_fields) {
+    return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
+  }
+  std::string tag_value;
+  if (!GetValue(fields[0], kAttributeCrypto, &tag_value, error)) {
+    return false;
+  }
+  int tag = 0;
+  if (!GetValueFromString(line, tag_value, &tag, error)) {
+    return false;
+  }
+  const std::string& crypto_suite = fields[1];
+  const std::string& key_params = fields[2];
+  std::string session_params;
+  if (fields.size() > 3) {
+    session_params = fields[3];
+  }
+  media_desc->AddCrypto(
+      CryptoParams(tag, crypto_suite, key_params, session_params));
+  return true;
+}
+
 // Updates or creates a new codec entry in the audio description with according
 // to `name`, `clockrate`, `bitrate`, and `channels`.
 void UpdateCodec(int payload_type,
diff --git a/pc/webrtc_sdp_unittest.cc b/pc/webrtc_sdp_unittest.cc
index 1daac0f..a2a884a 100644
--- a/pc/webrtc_sdp_unittest.cc
+++ b/pc/webrtc_sdp_unittest.cc
@@ -23,6 +23,7 @@
 #include "absl/memory/memory.h"
 #include "absl/strings/str_replace.h"
 #include "api/array_view.h"
+#include "api/crypto_params.h"
 #include "api/jsep_session_description.h"
 #include "api/media_types.h"
 #include "api/rtp_parameters.h"
@@ -54,6 +55,7 @@
 using cricket::Candidate;
 using cricket::ContentGroup;
 using cricket::ContentInfo;
+using cricket::CryptoParams;
 using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
 using cricket::ICE_CANDIDATE_COMPONENT_RTP;
 using cricket::kFecSsrcGroupSemantics;
@@ -139,9 +141,9 @@
   int maxaveragebitrate;
 };
 
-// Note: The reference strings do not contain a=fingerprint, which is
-// required for DTLS negotiation.
-// Neither do they contain the obsolete a=crypto lines.
+// TODO(deadbeef): In these reference strings, use "a=fingerprint" by default
+// instead of "a=crypto", and have an explicit test for adding "a=crypto".
+// Currently it's the other way around.
 
 // Reference sdp string
 static const char kSdpFullString[] =
@@ -173,6 +175,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -198,6 +203,8 @@
     "a=ice-ufrag:ufrag_video\r\na=ice-pwd:pwd_video\r\n"
     "a=mid:video_content_name\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     "a=ssrc-group:FEC 2 3\r\n"
     "a=ssrc:2 cname:stream_1_cname\r\n"
@@ -225,6 +232,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -238,6 +248,8 @@
     "a=ice-ufrag:ufrag_video\r\na=ice-pwd:pwd_video\r\n"
     "a=mid:video_content_name\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     "a=ssrc-group:FEC 2 3\r\n"
     "a=ssrc:2 cname:stream_1_cname\r\n"
@@ -370,6 +382,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -383,6 +398,8 @@
     "a=bundle-only\r\n"
     "a=mid:video_content_name\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     "a=ssrc-group:FEC 2 3\r\n"
     "a=ssrc:2 cname:stream_1_cname\r\n"
@@ -425,6 +442,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -454,6 +474,8 @@
     "a=ice-ufrag:ufrag_video\r\na=ice-pwd:pwd_video\r\n"
     "a=mid:video_content_name\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     "a=ssrc-group:FEC 2 3\r\n"
     "a=ssrc:2 cname:stream_1_cname\r\n"
@@ -506,6 +528,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -530,6 +555,8 @@
     "a=mid:video_content_name\r\n"
     "a=msid:local_stream_1 video_track_id_1\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     "a=ssrc-group:FEC 2 3\r\n"
     "a=ssrc:2 cname:stream_1_cname\r\n"
@@ -544,6 +571,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -556,6 +586,8 @@
     "a=mid:video_content_name_2\r\n"
     "a=msid:local_stream_2 video_track_id_2\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     "a=ssrc:5 cname:stream_2_cname\r\n"
     // Video track 3, stream 2.
@@ -566,6 +598,8 @@
     "a=mid:video_content_name_3\r\n"
     "a=msid:local_stream_2 video_track_id_3\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     "a=ssrc:6 cname:stream_2_cname\r\n";
 
@@ -607,6 +641,9 @@
     "a=msid:local_stream_1 audio_track_id_1\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -625,6 +662,9 @@
     "a=msid:local_stream_2 audio_track_id_2\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -644,6 +684,9 @@
     "a=msid:- audio_track_id_3\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -683,6 +726,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -706,6 +752,8 @@
     "a=mid:video_content_name\r\n"
     "a=msid:local_stream_1 video_track_id_1\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     // Audio track 2, stream 2.
     "m=audio 9 RTP/SAVPF 111 103 104\r\n"
@@ -717,6 +765,9 @@
     "a=sendrecv\r\n"
     "a=rtcp-mux\r\n"
     "a=rtcp-rsize\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
     "a=rtpmap:111 opus/48000/2\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
@@ -728,6 +779,8 @@
     "a=mid:video_content_name_2\r\n"
     "a=msid:local_stream_2 video_track_id_2\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n"
     // Video track 3, stream 2.
     "m=video 9 RTP/SAVPF 120\r\n"
@@ -737,6 +790,8 @@
     "a=mid:video_content_name_3\r\n"
     "a=msid:local_stream_2 video_track_id_3\r\n"
     "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
     "a=rtpmap:120 VP8/90000\r\n";
 
 // One candidate reference string as per W3c spec.
@@ -1216,6 +1271,10 @@
     AudioContentDescription* audio = new AudioContentDescription();
     audio->set_rtcp_mux(true);
     audio->set_rtcp_reduced_size(true);
+    audio->AddCrypto(CryptoParams(
+        1, "AES_CM_128_HMAC_SHA1_32",
+        "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32",
+        "dummy_session_params"));
     audio->set_protocol(cricket::kMediaProtocolSavpf);
     audio->AddCodec(AudioCodec(111, "opus", 48000, 0, 2));
     audio->AddCodec(AudioCodec(103, "ISAC", 16000, 0, 1));
@@ -1289,6 +1348,9 @@
   // configuration.
   VideoContentDescription* CreateVideoContentDescription() {
     VideoContentDescription* video = new VideoContentDescription();
+    video->AddCrypto(CryptoParams(
+        1, "AES_CM_128_HMAC_SHA1_80",
+        "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32", ""));
     video->set_protocol(cricket::kMediaProtocolSavpf);
     video->AddCodec(
         VideoCodec(120, JsepSessionDescription::kDefaultVideoCodecName));
@@ -1309,6 +1371,20 @@
     // rtcp_reduced_size
     EXPECT_EQ(cd1->rtcp_reduced_size(), cd2->rtcp_reduced_size());
 
+    // cryptos
+    EXPECT_EQ(cd1->cryptos().size(), cd2->cryptos().size());
+    if (cd1->cryptos().size() != cd2->cryptos().size()) {
+      ADD_FAILURE();
+      return;
+    }
+    for (size_t i = 0; i < cd1->cryptos().size(); ++i) {
+      const CryptoParams c1 = cd1->cryptos().at(i);
+      const CryptoParams c2 = cd2->cryptos().at(i);
+      EXPECT_TRUE(c1.Matches(c2));
+      EXPECT_EQ(c1.key_params, c2.key_params);
+      EXPECT_EQ(c1.session_params, c2.session_params);
+    }
+
     // protocol
     // Use an equivalence class here, for old and new versions of the
     // protocol description.
@@ -1486,11 +1562,6 @@
                                  const JsepSessionDescription& desc2) {
     EXPECT_EQ(desc1.session_id(), desc2.session_id());
     EXPECT_EQ(desc1.session_version(), desc2.session_version());
-    EXPECT_TRUE(desc1.description());
-    EXPECT_TRUE(desc2.description());
-    if (!desc1.description() || !desc2.description()) {
-      return false;
-    }
     CompareSessionDescription(*desc1.description(), *desc2.description());
     if (desc1.number_of_mediasections() != desc2.number_of_mediasections())
       return false;
@@ -1604,6 +1675,11 @@
                      absl::WrapUnique(video_desc_));
   }
 
+  void RemoveCryptos() {
+    audio_desc_->set_cryptos(std::vector<CryptoParams>());
+    video_desc_->set_cryptos(std::vector<CryptoParams>());
+  }
+
   // Removes everything in StreamParams from the session description that is
   // used for a=ssrc lines.
   void RemoveSsrcSignalingFromStreamParams() {
@@ -2007,7 +2083,7 @@
   EXPECT_EQ("", webrtc::SdpSerialize(jdesc_empty));
 }
 
-// This tests serialization of SDP with a=fingerprint, as would be
+// This tests serialization of SDP with a=crypto and a=fingerprint, as would be
 // the case in a DTLS offer.
 TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithFingerprint) {
   AddFingerprint();
@@ -2026,6 +2102,7 @@
 // be the case in a DTLS answer.
 TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithFingerprintNoCryptos) {
   AddFingerprint();
+  RemoveCryptos();
   JsepSessionDescription jdesc_with_fingerprint(kDummyType);
   MakeDescriptionWithoutCandidates(&jdesc_with_fingerprint);
   std::string message = webrtc::SdpSerialize(jdesc_with_fingerprint);
@@ -3178,6 +3255,8 @@
   // ssrc
   ExpectParseFailure("a=ssrc:1", "a=ssrc:badvalue");
   ExpectParseFailure("a=ssrc-group:FEC 2 3", "a=ssrc-group:FEC badvalue 3");
+  // crypto
+  ExpectParseFailure("a=crypto:1 ", "a=crypto:badvalue ");
   // rtpmap
   ExpectParseFailure("a=rtpmap:111 ", "a=rtpmap:badvalue ");
   ExpectParseFailure("opus/48000/2", "opus/badvalue/2");
@@ -3728,7 +3807,7 @@
   MakeUnifiedPlanDescription();
 
   JsepSessionDescription deserialized_description(kDummyType);
-  ASSERT_TRUE(
+  EXPECT_TRUE(
       SdpDeserialize(kUnifiedPlanSdpFullString, &deserialized_description));
 
   EXPECT_TRUE(CompareSessionDescription(jdesc_, deserialized_description));
diff --git a/pc/webrtc_session_description_factory.cc b/pc/webrtc_session_description_factory.cc
index c52ff2d..995ef5e 100644
--- a/pc/webrtc_session_description_factory.cc
+++ b/pc/webrtc_session_description_factory.cc
@@ -152,10 +152,13 @@
   RTC_DCHECK(signaling_thread_);
 
   if (!dtls_enabled) {
-    RTC_LOG(LS_INFO) << "DTLS is disabled, no encryption applied";
+    SetSdesPolicy(cricket::SEC_REQUIRED);
+    RTC_LOG(LS_VERBOSE) << "DTLS-SRTP disabled.";
     return;
   }
-  RTC_DCHECK(certificate || cert_generator_);
+
+  // SRTP-SDES is disabled if DTLS is on.
+  SetSdesPolicy(cricket::SEC_DISABLED);
   if (certificate) {
     // Use `certificate`.
     certificate_request_state_ = CERTIFICATE_WAITING;
@@ -286,6 +289,15 @@
   }
 }
 
+void WebRtcSessionDescriptionFactory::SetSdesPolicy(
+    cricket::SecurePolicy secure_policy) {
+  session_desc_factory_.set_secure(secure_policy);
+}
+
+cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() const {
+  return session_desc_factory_.secure();
+}
+
 void WebRtcSessionDescriptionFactory::OnMessage(rtc::Message* msg) {
   switch (msg->message_id) {
     case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
@@ -482,6 +494,7 @@
   on_certificate_ready_(certificate);
 
   transport_desc_factory_.set_certificate(certificate);
+  transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
 
   while (!create_session_description_requests_.empty()) {
     if (create_session_description_requests_.front().type ==
diff --git a/pc/webrtc_session_description_factory.h b/pc/webrtc_session_description_factory.h
index 60c8bc4..8e80fb5 100644
--- a/pc/webrtc_session_description_factory.h
+++ b/pc/webrtc_session_description_factory.h
@@ -104,6 +104,9 @@
   void CreateAnswer(CreateSessionDescriptionObserver* observer,
                     const cricket::MediaSessionOptions& session_options);
 
+  void SetSdesPolicy(cricket::SecurePolicy secure_policy);
+  cricket::SecurePolicy SdesPolicy() const;
+
   void set_enable_encrypted_rtp_header_extensions(bool enable) {
     session_desc_factory_.set_enable_encrypted_rtp_header_extensions(enable);
   }