質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
OAuth

OAuth(Open Authorization)は、APIを通して保護されたリソース(サードパーティのアプリケーション)へアクセスする為のオープンプロトコルです。

Twitter

Twitterは、140文字以内の「ツイート」と呼ばれる短文を投稿できるサービスです。Twitter上のほぼ全ての機能に対応するAPIが存在し、その関連サービスが多く公開されています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

0回答

1092閲覧

OAuth1.0のシグネチャの生成値がおかしくなる

tadanoosakana

総合スコア30

OAuth

OAuth(Open Authorization)は、APIを通して保護されたリソース(サードパーティのアプリケーション)へアクセスする為のオープンプロトコルです。

Twitter

Twitterは、140文字以内の「ツイート」と呼ばれる短文を投稿できるサービスです。Twitter上のほぼ全ての機能に対応するAPIが存在し、その関連サービスが多く公開されています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2019/06/22 21:25

編集2019/06/23 11:56

前提・実現したいこと

Twitter APIをKotlinで触っています。
OAuth 1.0のAPIでは oauth_signature というものを生成する必要があり、以下のページの入力例を使って実際に生成してみました。

Creating a signature — Twitter Developers

発生している問題・エラーメッセージ

問題はsigning keyの値とsignature base stringがあっているのもかかわらず、シグネチャの生成結果が異なるということです。

Text

1signing key: 2私のコードで生成された値: kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE 3このページに載っている正しい値: kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE 4 5signature base string: 6私のコードで生成された値: POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252b%2520Gentlemen%252c%2520a%2520signed%2520OAuth%2520request%2521 7このページに載っている正しい値: POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521 8 9signature: 10私のコードで生成された値: lzKEyPhir88fiRVm0hC22SpU6Ig= 11このページに載っている正しい値: hCtSmYh+iHYCEqBWrE7C7hYmtUk=

該当のソースコード

私のコードを以下に示します。

Kotlin

1import java.net.URLEncoder 2import java.util.* 3import javax.crypto.Mac 4import javax.crypto.spec.SecretKeySpec 5 6fun main() { 7 val apiSecretKey = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw" 8 val accessTokenSecret = "LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE" 9 val requestMethod = "POST" 10 val requestUrl = "https://api.twitter.com/1.1/statuses/update.json" 11 val parameters = mapOf( 12 "include_entities" to "true", 13 "oauth_consumer_key" to "xvz1evFS4wEEPTGEFPHBog", 14 "oauth_nonce" to "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", 15 "oauth_signature_method" to "HMAC-SHA1", 16 "oauth_timestamp" to "1318622958", 17 "oauth_token" to "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", 18 "oauth_version" to "1.0", 19 "status" to "Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21" 20 ) 21 22 val signature = createSignature(apiSecretKey, accessTokenSecret, requestMethod, requestUrl, parameters) 23 val expected = "hCtSmYh+iHYCEqBWrE7C7hYmtUk=" 24 25 println(if (signature == expected) "正しい結果が得られました。" else "間違った結果が得られました。") 26} 27 28fun createSignature( 29 apiSecretKey: String, 30 accessTokenSecret: String, 31 requestMethod: String, 32 requestUrl: String, 33 parameters: Map<String, String> 34): String { 35 36 // キーを生成する。 37 val encodedApiSecretKey = URLEncoder.encode(apiSecretKey, Charsets.UTF_8) 38 val encodedAccessTokenSecret = URLEncoder.encode(accessTokenSecret, Charsets.UTF_8) 39 val key = "$encodedApiSecretKey&$encodedAccessTokenSecret" 40 println("key: $key") 41 42 // データを生成する。 43 val encodedMethod = URLEncoder.encode(requestMethod, Charsets.UTF_8) 44 val encodedUrl = URLEncoder.encode(requestUrl, Charsets.UTF_8) 45 val encodedParameters = URLEncoder.encode(parameters.asSequence().sortedBy { it.key }.map { "${it.key}=${it.value}" }.joinToString("&"), Charsets.UTF_8) 46 val data = "$encodedMethod&$encodedUrl&$encodedParameters" 47 println("data: $data") 48 49 // キーとデータからシグネチャを生成する。 50 val algorithm = "HmacSHA1" 51 val keySpec = SecretKeySpec(key.toByteArray(), algorithm) 52 val mac = Mac.getInstance(algorithm).also { it.init(keySpec) } 53 val digest = mac.doFinal(data.toByteArray()) 54 val signature = Base64.getEncoder().encodeToString(digest) 55 println("signature: $signature") 56 57 return signature 58}

実行結果も以下に示します。

Text

1key: kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE 2data: POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252b%2520Gentlemen%252c%2520a%2520signed%2520OAuth%2520request%2521 3signature: lzKEyPhir88fiRVm0hC22SpU6Ig= 4間違った結果が得られました。

試したこと

ためしに、公式ページの値をコピペして、それらからシグネチャを生成する処理のみを走らせてみました。
コードを以下に示します。

Kotlin

1package net.aridai.mykotlin 2 3import java.util.* 4import javax.crypto.Mac 5import javax.crypto.spec.SecretKeySpec 6 7fun main() { 8 9 val signingKey = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE" 10 val signatureBaseString = "POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521" 11 12 val algorithm = "HmacSHA1" 13 val keySpec = SecretKeySpec(signingKey.toByteArray(), algorithm) 14 val mac = Mac.getInstance(algorithm).also { it.init(keySpec) } 15 val digest = mac.doFinal(signatureBaseString.toByteArray()) 16 val signature = Base64.getEncoder().encodeToString(digest) 17 println("$signature") 18}

実行結果を以下に示します。

Text

1hCtSmYh+iHYCEqBWrE7C7hYmtUk=

結果より、シグネチャの生成処理自体は正しく動作をしていると言えます。

補足情報(FW/ツールのバージョンなど)

IntelliJ IDEA 2018.3.6 (Community Edition)
Build #IC-183.6156.11, built on March 25, 2019
JRE: 1.8.0_152-release-1343-b28 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.14.5

build.gradle.ktsを以下に示します。

Kotlin

1import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 3plugins { 4 java 5 kotlin("jvm") version "1.3.40" 6} 7 8group = "tadano.sakana" 9version = "1.0.0" 10 11repositories { 12 mavenCentral() 13} 14 15dependencies { 16 implementation(kotlin("stdlib-jdk8")) 17 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1") 18 implementation("io.reactivex.rxjava2:rxjava:2.2.9") 19 implementation("io.reactivex.rxjava2:rxkotlin:2.3.0") 20 implementation("com.squareup.retrofit2:retrofit:2.6.0") 21 implementation("com.squareup.retrofit2:converter-moshi:2.6.0") 22 implementation("com.squareup.moshi:moshi:1.8.0") 23 implementation("com.squareup.moshi:moshi-kotlin:1.8.0") 24 implementation("com.squareup.okhttp3:okhttp:3.14.2") 25 26 testCompile("junit", "junit", "4.12") 27} 28 29configure<JavaPluginConvention> { 30 sourceCompatibility = JavaVersion.VERSION_1_8 31} 32tasks.withType<KotlinCompile> { 33 kotlinOptions.jvmTarget = "1.8" 34}

追記

次のコードで検証して見ると、大文字小文字が違っていました。
公式ページの値と比較する際にChromeの検索機能 (大文字小文字を区別しない) で比較していたので気づきませんでした。

Kotlin

1val expected = "長いので略" 2val actual = "長いので略" 3val lastIndex = min(expected.length, actual.length) 4 5for (i in (0..lastIndex)) { 6 val actualChar = actual[i] 7 val expectedChar = expected[i] 8 if (actualChar != expectedChar) 9 println("$i: $actualChar, $expectedChar") 10}

Text

1388: b, B 2407: c, C

この情報をもとにもう一度実装し直してみます。
ページに大文字小文字についての言及があったのかもしれないので。

追記2

Hello Ladies + Gentlemen, a signed OAuth request!をパーセントエンコードするとHello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21になると思いますが、

Creating a signature — Twitter Developers

ではHello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21として扱われているように思えます。
+%2Bに変換されるはずですが、このページでは%2bとして扱われています。
また、,%2Cに変換されるはずですが、%2cとして扱われています。

しかし、Creating a signature — Twitter Developers で実際のシグネチャを求めていく解説の中では、正しい値である大文字のBと大文字のCの方として扱っているため、実行結果が異なるといったことにつながったのではないかと思います。

試しに、入力を修正して私のプログラムを実行してみました。
"status" to "Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21"
"status" to "Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21" に直して実行すると、

Text

1key: kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE 2data: POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521 3signature: hCtSmYh+iHYCEqBWrE7C7hYmtUk= 4正しい結果が得られました。

というように正しく動作したように思えます。

この「解説ページが間違っている」という推測が正しいという裏付けが取れませんので、何か情報を持ってらっしゃる方は教えてください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問