@@ -51,6 +51,12 @@ async Task Authenticate(string username, NpgsqlTimeout timeout, bool async, Canc
5151 await AuthenticateSHA256 ( username , ( AuthenticationSHA256PasswordMessage ) msg , async, cancellationToken ) . ConfigureAwait ( false ) ;
5252 break ;
5353
54+ case AuthenticationRequestType . MD5SHA256Password :
55+ ThrowIfNotAllowed ( requiredAuthModes , RequireAuthMode . ScramSHA256 ) ;
56+ await AuthenticateMD5SHA256 ( username , ( AuthenticationMD5SHA256PasswordMessage ) msg , async, cancellationToken )
57+ . ConfigureAwait ( false ) ;
58+ break ;
59+
5460 case AuthenticationRequestType . GSS :
5561 case AuthenticationRequestType . SSPI :
5662 ThrowIfNotAllowed ( requiredAuthModes , msg . AuthRequestType == AuthenticationRequestType . GSS ? RequireAuthMode . GSS : RequireAuthMode . SSPI ) ;
@@ -237,22 +243,62 @@ async Task AuthenticateSHA256(string username, AuthenticationSHA256PasswordMessa
237243
238244 var result = new byte [ hValue . Length * 2 + 1 ] ;
239245 BytesToHex ( hValue , result , 0 , hValue . Length ) ;
240- await WriteSHA256Response ( result , async , cancellationToken ) . ConfigureAwait( false ) ;
246+ await WritePassword ( result , async , cancellationToken ) . ConfigureAwait( false ) ;
241247 await Flush ( async , cancellationToken ) . ConfigureAwait( false ) ;
248+ }
249+
250+ async Task AuthenticateMD5SHA256 ( string username , AuthenticationMD5SHA256PasswordMessage message , bool async , CancellationToken cancellationToken = default )
251+ {
252+ var password = await GetPassword ( username , async , cancellationToken ) . ConfigureAwait( false) ;
253+ if ( string . IsNullOrEmpty ( password ) )
254+ throw new NpgsqlException ( "No password has been provided but the backend requires one (in SHA256)" ) ;
255+
256+ var passwordBytes = NpgsqlWriteBuffer . UTF8Encoding . GetBytes ( password ) ;
257+
258+ // https://github.com/HuaweiCloudDeveloper/gaussdb-r2dbc/blob/54783aa7ba09731300b31d9cf366185d0bf50447/src/main/java/io/r2dbc/gaussdb/util/MD5Digest.java#L227
259+ var randomCodeBytes = Convert . FromHexString ( message . RandomCode ) ;
260+
261+ var passwordKeyBytes = Rfc2898DeriveBytes . Pbkdf2 ( passwordBytes , randomCodeBytes ,
262+ 2048 , HashAlgorithmName . SHA1 , 32
263+ ) ;
264+
265+ var serverKey = HMACSHA256 . HashData ( passwordKeyBytes , "Sever Key"u8 ) ;
266+ var clientKey = HMACSHA256 . HashData ( passwordKeyBytes , "Client Key"u8 ) ;
267+ var storedKey = SHA256 . HashData ( clientKey ) ;
268+
269+ var stringBuilder = new StringBuilder ( ) ;
270+ stringBuilder . Append ( message . RandomCode ) ;
271+ stringBuilder . Append ( Convert . ToHexString ( serverKey ) . ToLowerInvariant ( ) ) ;
272+ stringBuilder . Append ( Convert . ToHexString ( storedKey ) . ToLowerInvariant ( ) ) ;
273+ var encryptedString = stringBuilder . ToString ( ) ;
242274
243- static void BytesToHex ( byte [ ] bytes , byte [ ] hex , int offset , int length )
275+ byte [ ] passDigest ;
276+ using ( var md5 = MD5 . Create ( ) )
244277 {
245- var lookup = new [ ] { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' } ;
246- var pos = offset ;
247- for ( var i = 0 ; i < length ; ++ i )
248- {
249- var c = bytes [ i ] & 255 ;
250- var j = c >> 4 ;
251- hex [ pos ++ ] = ( byte ) lookup [ j ] ;
252- j = c & 15 ;
253- hex [ pos ++ ] = ( byte ) lookup [ j ] ;
254- }
278+ // Convert the string to bytes using UTF-8 encoding
279+ var stringBytes = NpgsqlWriteBuffer . UTF8Encoding . GetBytes ( encryptedString ) ;
280+
281+ // Update the hash state with the string bytes
282+ // The 'null, 0' arguments are for the output buffer, which isn't needed here
283+ md5 . TransformBlock ( stringBytes , 0 , stringBytes . Length , null , 0 ) ;
284+
285+ // Update the hash state with the salt bytes and finalize the hash calculation
286+ // This is the final block of data being added.
287+ var saltBytes = message . Salt . ToArray ( ) ;
288+ md5 . TransformFinalBlock ( saltBytes , 0 , saltBytes . Length ) ;
289+
290+ // Retrieve the computed hash digest
291+ ArgumentNullException . ThrowIfNull ( md5 . Hash ) ;
292+ passDigest = md5 . Hash ;
255293 }
294+
295+ var result = new byte [ MD5 . HashSizeInBytes * 2 + 3 ] ;
296+ result [ 0 ] = ( byte ) 'm' ;
297+ result [ 1 ] = ( byte ) 'd' ;
298+ result [ 2 ] = ( byte ) '5' ;
299+ BytesToHex ( passDigest , result , 3 , MD5 . HashSizeInBytes ) ;
300+ await WritePassword ( result , async , cancellationToken ) . ConfigureAwait( false ) ;
301+ await Flush ( async , cancellationToken ) . ConfigureAwait( false ) ;
256302 }
257303
258304 internal async Task AuthenticateGSS ( bool async )
@@ -325,4 +371,18 @@ internal async Task AuthenticateGSS(bool async)
325371
326372 return password ;
327373 }
374+
375+ static void BytesToHex ( byte [ ] bytes , byte [ ] hex , int offset , int length )
376+ {
377+ var lookup = new [ ] { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' } ;
378+ var pos = offset ;
379+ for ( var i = 0 ; i < length ; ++ i )
380+ {
381+ var c = bytes [ i ] & 255 ;
382+ var j = c >> 4 ;
383+ hex [ pos ++ ] = ( byte ) lookup [ j ] ;
384+ j = c & 15 ;
385+ hex [ pos ++ ] = ( byte ) lookup [ j ] ;
386+ }
387+ }
328388}
0 commit comments