(I have found the answer. It is listed as an answer below, in case anyone else has the same problem.)
For a WordPress API call using OAuth 1.0, Postman creates PHP cURL code that works perfectly. So I know from trying their as-is code that the oauth_signature that they generate is correct.
But following every tutorial and Stack Overflow question I can find on this subject, I can't get my implementation to create an oauth_signature that matches the one that Postman generates by using the same variables.
Normally the nonce and the current time would be unique values, but of course I am copying them from Postman's data so that I have all the same data when trying to generate the signature to match the signature that Postman comes up with.
Here is my code, and this is exactly the data that was entered into Postman even though there is a dummy URL and dummy tokens. I am looking for help with the step of generating the signature, not actually connecting to the API (that part works fine).
My belief is that there is a problem with my method of generating the signature, because everything else matches, and the only information I do not have to check is the source code of Postman to see exactly how Postman generates the signature with the data you give it, so I am looking for help with this implementation.
Yes, I know there are libraries out there (and I have looked at some of them to compare the implementation of my code to theirs), but I am trying to do this without a library, since it really shouldn't be that hard.
<?php
$oauthconsumerkey = "oconsumerkey";
$oauthtoken = "otoken";
$clientsecret = "oclientsecret";
$tokensecret = "otokensecret";
$key = rawurlencode($clientsecret) . "&" . rawurlencode($tokensecret);
$method = "GET";
$baseurl = "https://test.com/wp-json/wp/v2/users/me/";
// these 2 variables were generated by Postman
$nonce = "1TT5UggZswB";
$timenow = "1548458598";
$paramstring = "oauth_consumer_key=" . $oauthconsumerkey . "&oauth_token=" . $oauthtoken . "&oauth_signature_method=HMAC-SHA1" . "&oauth_timestamp=" . $timenow . "&oauth_nonce=" . $nonce;
$encodeurl = $method . "&" . rawurlencode($baseurl) . "&" . rawurlencode($paramstring);
$signature = hash_hmac( 'sha1', $encodeurl, $key, TRUE );
$signature = base64_encode( $signature );
$curlurl = $baseurl . "?" . $paramstring . "&oauth_signature=" . $signature;
print $signature;
?>
From these variables, Postman gives a signature of: 4TJtCPkjrkBnIthdUujQ4j/RHtY=
But the above code gives a signature of: plKJeIp4tWC4J2RebyNuelr7vgg=
(I also realize there is a "&" in my string for generating the signature but a "?" in my cURL URL. The "?" in the cURL URL is how Postman has it, and it's required for the site to recognize the route. And just to be sure, I have tried generating the signature both ways, using "&" or "?", because I've seen different people saying different things about what is required in the spec for generating the signature.)
</div>
I figured out the problem! And the Twitter docs did help (https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature.html), so thanks @RamRaider.
My $paramstring components were in the wrong order. I took the order from their order in the cURL URL in the PHP code generated by Postman, but that order was some random order I guess and unrelated to the order required for generating the signature, which is alphabetical. That variable in my code should have been constructed like this:
$paramstring = "oauth_consumer_key=" . $oauthconsumerkey . "&oauth_nonce=" . $nonce . "&oauth_signature_method=HMAC-SHA1" . "&oauth_timestamp=" . $timenow . "&oauth_token=" . $oauthtoken;