diff --git a/eventlet/green/ssl.py b/eventlet/green/ssl.py
index 828f9bf..f6da878 100644
--- a/eventlet/green/ssl.py
+++ b/eventlet/green/ssl.py
@@ -191,18 +191,48 @@ class GreenSSLSocket(_original_sslsocket):
                     raise
 
     def recv(self, buflen=1024, flags=0):
+        return self._base_recv(buflen, flags, into=False)
+
+    def recv_into(self, buffer, nbytes=None, flags=0):
+        # Copied verbatim from CPython
+        if buffer and nbytes is None:
+            nbytes = len(buffer)
+        elif nbytes is None:
+            nbytes = 1024
+        # end of CPython code
+
+        return self._base_recv(nbytes, flags, into=True, buffer_=buffer)
+
+    def _base_recv(self, nbytes, flags, into, buffer_=None):
+        if into:
+            plain_socket_function = socket.recv_into
+        else:
+            plain_socket_function = socket.recv
+
         # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is
         if self._sslobj:
             if flags != 0:
                 raise ValueError(
-                    "non-zero flags not allowed in calls to recv() on %s" %
-                    self.__class__)
-            read = self.read(buflen)
+                    "non-zero flags not allowed in calls to %s() on %s" %
+                    plain_socket_function.__name__, self.__class__)
+            if sys.version_info < (2, 7) and into:
+                # Python 2.6 SSLSocket.read() doesn't support reading into
+                # a given buffer so we need to emulate
+                data = self.read(nbytes)
+                buffer_[:len(data)] = data
+                read = len(data)
+            elif into:
+                read = self.read(nbytes, buffer_)
+            else:
+                read = self.read(nbytes)
             return read
         else:
             while True:
                 try:
-                    return socket.recv(self, buflen, flags)
+                    args = [self, nbytes, flags]
+                    if into:
+                        args.insert(1, buffer_)
+                    return plain_socket_function(*args)
                 except orig_socket.error as e:
                     if self.act_non_blocking:
                         raise
@@ -218,12 +248,6 @@ class GreenSSLSocket(_original_sslsocket):
                         return b''
                     raise
 
-    def recv_into(self, buffer, nbytes=None, flags=0):
-        if not self.act_non_blocking:
-            trampoline(self, read=True, timeout=self.gettimeout(),
-                       timeout_exc=timeout_exc('timed out'))
-        return super(GreenSSLSocket, self).recv_into(buffer, nbytes, flags)
-
     def recvfrom(self, addr, buflen=1024, flags=0):
         if not self.act_non_blocking:
             trampoline(self, read=True, timeout=self.gettimeout(),
diff --git a/tests/ssl_test.py b/tests/ssl_test.py
index 13e090c..901e758 100644
--- a/tests/ssl_test.py
+++ b/tests/ssl_test.py
@@ -1,12 +1,15 @@
+import contextlib
 import socket
 import warnings
 
 import eventlet
 from eventlet import greenio
+from eventlet.green import socket
 try:
     from eventlet.green import ssl
 except ImportError:
     pass
+from eventlet.support import six
 import tests
 
 
@@ -213,3 +216,63 @@ class SSLTest(tests.LimitedTestCase):
         listener.close()
         loopt.wait()
         eventlet.sleep(0)
+
+    def test_receiving_doesnt_block_if_there_is_already_decrypted_buffered_data(self):
+        # Here's what could (and would) happen before the relevant bug was fixed (assuming method
+        # M was trampolining unconditionally before actually reading):
+        # 1. One side sends n bytes, leaves connection open (important)
+        # 2. The other side uses method M to read m (where m < n) bytes, the underlying SSL
+        #    implementation reads everything from the underlying socket, decrypts all n bytes,
+        #    returns m of them and buffers n-m to be read later.
+        # 3. The other side tries to read the remainder of the data (n-m bytes), this blocks
+        #    because M trampolines uncoditionally and trampoline will hang because reading from
+        #    the underlying socket would block. It would block because there's no data to be read
+        #    and the connection is still open; leaving the connection open /mentioned in 1./ is
+        #    important because otherwise trampoline would return immediately and the test would pass
+        #    even with the bug still present in the code).
+        #
+        # The solution is to first request data from the underlying SSL implementation and only
+        # trampoline if we actually need to read some data from the underlying socket.
+        #
+        # GreenSSLSocket.recv() wasn't broken but I've added code to test it as well for
+        # completeness.
+        content = b'xy'
+
+        def recv(sock, expected):
+            assert sock.recv(len(expected)) == expected
+
+        def recv_into(sock, expected):
+            buf = bytearray(len(expected))
+            assert sock.recv_into(buf, len(expected)) == len(expected)
+            assert buf == expected
+
+        for read_function in [recv, recv_into]:
+            print('Trying %s...' % (read_function,))
+            listener = listen_ssl_socket()
+
+            def accept(listener):
+                sock, addr = listener.accept()
+                sock.sendall(content)
+                return sock
+
+            accepter = eventlet.spawn(accept, listener)
+
+            client_to_server = None
+            try:
+                client_to_server = ssl.wrap_socket(eventlet.connect(listener.getsockname()))
+                for character in six.iterbytes(content):
+                    character = six.int2byte(character)
+                    print('We have %d already decrypted bytes pending, expecting: %s' % (
+                        client_to_server.pending(), character))
+                    read_function(client_to_server, character)
+            finally:
+                if client_to_server is not None:
+                    client_to_server.close()
+                server_to_client = accepter.wait()
+
+                # Very important: we only want to close the socket *after* the other side has
+                # read the data it wanted already, otherwise this would defeat the purpose of the
+                # test (see the comment at the top of this test).
+                server_to_client.close()
+
+                listener.close()
